br_invoices_pdf 0.1.5 → 0.2.0.alpha.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +10 -0
  5. data/CHANGELOG.md +16 -0
  6. data/bin/console +5 -3
  7. data/br_invoices_pdf.gemspec +11 -8
  8. data/cfe.xml +2 -0
  9. data/lib/br_invoices_pdf.rb +36 -0
  10. data/lib/br_invoices_pdf/cfe.rb +10 -0
  11. data/lib/br_invoices_pdf/cfe/parser.rb +35 -0
  12. data/lib/br_invoices_pdf/cfe/parser/access_key.rb +19 -0
  13. data/lib/br_invoices_pdf/cfe/parser/base_parser.rb +19 -0
  14. data/lib/br_invoices_pdf/cfe/parser/company_attributes.rb +45 -0
  15. data/lib/br_invoices_pdf/cfe/parser/cpf.rb +15 -0
  16. data/lib/br_invoices_pdf/cfe/parser/document_number.rb +15 -0
  17. data/lib/br_invoices_pdf/cfe/parser/fisco_obs.rb +23 -0
  18. data/lib/br_invoices_pdf/cfe/parser/payment.rb +22 -0
  19. data/lib/br_invoices_pdf/cfe/parser/payments.rb +45 -0
  20. data/lib/br_invoices_pdf/cfe/parser/products_data.rb +37 -0
  21. data/lib/br_invoices_pdf/cfe/parser/sat.rb +25 -0
  22. data/lib/br_invoices_pdf/cfe/renderer.rb +49 -0
  23. data/lib/br_invoices_pdf/cfe/renderer/base_renderer.rb +72 -0
  24. data/lib/br_invoices_pdf/cfe/renderer/company_identification.rb +36 -0
  25. data/lib/br_invoices_pdf/cfe/renderer/fisco_info.rb +26 -0
  26. data/lib/br_invoices_pdf/cfe/renderer/header.rb +36 -0
  27. data/lib/br_invoices_pdf/cfe/renderer/payment_forms.rb +54 -0
  28. data/lib/br_invoices_pdf/cfe/renderer/product_table.rb +68 -0
  29. data/lib/br_invoices_pdf/cfe/renderer/qr_code.rb +110 -0
  30. data/lib/br_invoices_pdf/cfe/renderer/taxes_info.rb +39 -0
  31. data/lib/br_invoices_pdf/cfe/renderer/totals.rb +50 -0
  32. data/lib/br_invoices_pdf/errors/invalid_document_type.rb +12 -0
  33. data/lib/br_invoices_pdf/generator.rb +17 -0
  34. data/lib/br_invoices_pdf/version.rb +1 -1
  35. metadata +130 -6
@@ -0,0 +1,25 @@
1
+ module BrInvoicesPdf
2
+ module Cfe
3
+ module Parser
4
+ module Sat
5
+ extend BaseParser
6
+
7
+ module_function
8
+
9
+ AVAILABLE_UF = { '35' => 'São Paulo' }.freeze
10
+
11
+ def execute(xml)
12
+ {
13
+ pos_number: locate_element(xml, 'infCFe/ide/numeroCaixa'),
14
+ ncfe_number: locate_element(xml, 'infCFe/ide/nCFe'),
15
+ uf: AVAILABLE_UF[locate_element(xml, 'infCFe/ide/cUF')],
16
+ sat_number: locate_element(xml, 'infCFe/ide/nserieSAT'),
17
+ emission_date: locate_element(xml, 'infCFe/ide/dEmi'),
18
+ emission_hour: locate_element(xml, 'infCFe/ide/hEmi'),
19
+ document_qr_code_signature: locate_element(xml, 'infCFe/ide/assinaturaQRCODE')
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ require 'br_invoices_pdf/cfe/renderer/company_identification'
2
+ require 'br_invoices_pdf/cfe/renderer/fisco_info'
3
+ require 'br_invoices_pdf/cfe/renderer/header'
4
+ require 'br_invoices_pdf/cfe/renderer/payment_forms'
5
+ require 'br_invoices_pdf/cfe/renderer/product_table'
6
+ require 'br_invoices_pdf/cfe/renderer/qr_code'
7
+ require 'br_invoices_pdf/cfe/renderer/taxes_info'
8
+ require 'br_invoices_pdf/cfe/renderer/totals'
9
+
10
+ module BrInvoicesPdf
11
+ module Cfe
12
+ module Renderer
13
+ module_function
14
+
15
+ AUTO_HEIGHT_MOCK = 2000
16
+
17
+ RENDERERS = [
18
+ CompanyIdentification,
19
+ Header,
20
+ ProductTable,
21
+ Totals,
22
+ PaymentForms,
23
+ TaxesInfo,
24
+ QrCode,
25
+ FiscoInfo
26
+ ].freeze
27
+
28
+ # :reek:FeatureEnvy
29
+ def pdf(data, options)
30
+ page_width = Renderer::BaseRenderer.page_paper_width(options[:page_size])
31
+
32
+ Prawn::Document.new(options.merge(page_size: [page_width, AUTO_HEIGHT_MOCK])) do |pdf|
33
+ pdf_content(pdf, data, page_width)
34
+ end
35
+ end
36
+
37
+ def pdf_content(pdf, data, page_width)
38
+ pdf.font_size(7) do
39
+ RENDERERS.each do |renderer|
40
+ renderer.execute(pdf, data)
41
+ end
42
+
43
+ page = pdf.page
44
+ page.dictionary.data[:MediaBox] = [0, pdf.y - page.margins[:bottom], page_width, AUTO_HEIGHT_MOCK]
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,72 @@
1
+ module BrInvoicesPdf
2
+ module Cfe
3
+ module Renderer
4
+ module BaseRenderer
5
+ module_function
6
+
7
+ ADDRESS_FORMAT = '%s, %s, %s, %s, %s/%s'.freeze
8
+ def format_address(address)
9
+ ADDRESS_FORMAT % %i[public_place number complement neighborhood city state].map(&address.method(:[]))
10
+ end
11
+
12
+ def box(pdf, position, width)
13
+ pdf.bounding_box(position, width: width) do
14
+ pdf.pad(2) do
15
+ pdf.indent(2, 2) do
16
+ yield
17
+ end
18
+ end
19
+
20
+ pdf.stroke_bounds
21
+ end
22
+ end
23
+
24
+ # :reek:FeatureEnvy
25
+ def pdf_setup(pdf)
26
+ pdf.bounding_box([0, pdf.cursor], width: page_content_width(pdf)) do
27
+ pdf.pad(10) do
28
+ pdf.indent(10, 10) do
29
+ yield
30
+ end
31
+ end
32
+ pdf.stroke_bounds
33
+ end
34
+ end
35
+
36
+ CNPJ_FORMAT = '%d.%d.%d/%d-%d'.freeze
37
+ # :reek:FeatureEnvy
38
+ def format_cnpj(cnpj)
39
+ format(CNPJ_FORMAT, cnpj[0, 2], cnpj[2, 3], cnpj[5, 3], cnpj[8, 4], cnpj[12, 2])
40
+ end
41
+
42
+ CPF_FORMAT = '%d.%d.%d-%d'.freeze
43
+ # :reek:FeatureEnvy
44
+ def format_cpf(cpf)
45
+ format(CPF_FORMAT, cpf[0, 3], cpf[3, 3], cpf[6, 3], cpf[9, 2])
46
+ end
47
+
48
+ # :reek:FeatureEnvy
49
+ def format_currency(num)
50
+ num.truncate.to_s.reverse.split(/.../).join('.').reverse + format(',%02d', (num.frac * 100).truncate)
51
+ end
52
+
53
+ # :reek:FeatureEnvy
54
+ def format_number(num, prec: 4)
55
+ num.truncate.to_s + (prec > 0 ? format(",%0#{prec}d", (num.frac * 10**prec).truncate) : '')
56
+ end
57
+
58
+ # :reek:FeatureEnvy
59
+ def page_paper_width(name)
60
+ (name.is_a?(Array) ? name : PDF::Core::PageGeometry::SIZES[name]).first
61
+ end
62
+
63
+ # :reek:FeatureEnvy
64
+ def page_content_width(pdf)
65
+ page = pdf.page
66
+ margins = page.margins
67
+ page_paper_width(page.size) - margins[:left] - margins[:right]
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,36 @@
1
+ module BrInvoicesPdf
2
+ module Cfe
3
+ module Renderer
4
+ # :reek:DataClump
5
+ module CompanyIdentification
6
+ extend BaseRenderer
7
+
8
+ module_function
9
+
10
+ def execute(pdf, data)
11
+ attributes = data[:company_attributes]
12
+ pdf_setup(pdf) do
13
+ company_params(pdf, attributes)
14
+ end
15
+ end
16
+
17
+ # :reek:FeatureEnvy
18
+ def company_params(pdf, data)
19
+ pdf.text(data[:trading_name], align: :center)
20
+ pdf.text(data[:company_name], align: :center)
21
+ pdf.text(format_address(data[:address]), align: :center)
22
+ insert_fiscal_numbers(pdf, data)
23
+ end
24
+ private_class_method :company_params
25
+
26
+ # :reek:FeatureEnvy
27
+ def insert_fiscal_numbers(pdf, data)
28
+ pdf.text('CNPJ: ' + format_cnpj(data[:cnpj]), align: :center)
29
+ pdf.text('Inscrição Estadual: ' + data[:ie], align: :center)
30
+ pdf.text('Inscrição Municipal: ' + data[:im], align: :center)
31
+ end
32
+ private_class_method :insert_fiscal_numbers
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ module BrInvoicesPdf
2
+ module Cfe
3
+ module Renderer
4
+ module FiscoInfo
5
+ extend BaseRenderer
6
+
7
+ module_function
8
+
9
+ # :reek:FeatureEnvy
10
+ def execute(pdf, data)
11
+ box(pdf, [0, pdf.cursor], page_content_width(pdf)) do
12
+ pdf.text("Observações do fisco\n\n", style: :italic)
13
+ add_fisco_obs(pdf, data[:fisco_obs])
14
+ end
15
+ end
16
+
17
+ def add_fisco_obs(pdf, fisco_obs)
18
+ fisco_obs.each do |element|
19
+ pdf.text(element[:field] + ': ' + element[:text], align: :center)
20
+ end
21
+ end
22
+ private_class_method :add_fisco_obs
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,36 @@
1
+ module BrInvoicesPdf
2
+ module Cfe
3
+ module Renderer
4
+ module Header
5
+ extend BaseRenderer
6
+
7
+ module_function
8
+
9
+ # :reek:FeatureEnvy
10
+ def execute(pdf, data)
11
+ cpf = cpf_vlue(data[:cpf])
12
+
13
+ pdf_setup(pdf) do
14
+ add_header_config(pdf, data, cpf)
15
+ end
16
+
17
+ pdf.move_down(5)
18
+ end
19
+
20
+ def cpf_vlue(cpf)
21
+ cpf ? 'CONSUMIDOR: ' + format_cpf(cpf) : 'CONSUMIDOR NAO IDENTIFICADO'
22
+ end
23
+ private_class_method :cpf_vlue
24
+
25
+ def add_header_config(pdf, data, cpf)
26
+ pdf.font('Helvetica', style: :bold)
27
+ pdf.text("Extrato: #{data[:document_number]}", align: :center)
28
+ pdf.text(cpf, align: :center)
29
+ pdf.text('CUPOM FISCAL ELETRONICO - SAT', align: :center)
30
+ pdf.font('Helvetica', style: :normal, align: :center)
31
+ end
32
+ private_class_method :add_header_config
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,54 @@
1
+ module BrInvoicesPdf
2
+ module Cfe
3
+ module Renderer
4
+ module PaymentForms
5
+ extend BaseRenderer
6
+
7
+ module_function
8
+
9
+ def execute(pdf, data)
10
+ pdf.font_size(6) do
11
+ width = page_content_width(pdf)
12
+ table_data = payments_table_data(data)
13
+ render_table(pdf, table_data, width)
14
+ end
15
+
16
+ pdf.move_down(5)
17
+ end
18
+
19
+ def render_table(pdf, table_data, width)
20
+ pdf.table(table_data, width: width) do |table|
21
+ format_table(table, table_data)
22
+ end
23
+ end
24
+ private_class_method :render_table
25
+
26
+ # :reek:FeatureEnvy
27
+ def format_table(table, table_data)
28
+ table.columns([0, 1]).valign = :center
29
+ table.columns(1).align = :right
30
+ table_size = table_data.size
31
+ table.row([0, table_size - 1]).font_style = :bold
32
+ table.row([0, table_size - 2]).font_style = :bold
33
+ end
34
+ private_class_method :format_table
35
+
36
+ PAYMENTS_TABLE_BASE_DATA = [['FORMA DE PAGAMENTO', 'VALOR']].freeze
37
+ def payments_table_data(data)
38
+ payments_data = data[:payments].reduce(PAYMENTS_TABLE_BASE_DATA) do |result, cur|
39
+ result + [[cur[:type], format_currency(BigDecimal(cur[:amount]))]]
40
+ end
41
+
42
+ add_default_values(payments_data, data[:payment])
43
+ end
44
+ private_class_method :payments_table_data
45
+
46
+ def add_default_values(payments_data, data)
47
+ payments_data.push(['TROCO', format_currency(BigDecimal(data[:cashback]))])
48
+ payments_data.push(['TOTAL', format_currency(BigDecimal(data[:paid]))])
49
+ end
50
+ private_class_method :add_default_values
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,68 @@
1
+ module BrInvoicesPdf
2
+ module Cfe
3
+ module Renderer
4
+ module ProductTable
5
+ extend BaseRenderer
6
+
7
+ module_function
8
+
9
+ def execute(pdf, data)
10
+ table_data = product_table_data(data)
11
+
12
+ pdf.font_size(6) do
13
+ format_table(pdf, table_data)
14
+ end
15
+
16
+ pdf.move_down(5)
17
+ end
18
+
19
+ def format_table(pdf, table_data)
20
+ width = page_content_width(pdf)
21
+ pdf.table(table_data, width: width) do |table|
22
+ format_row(table.row(0))
23
+ format_columns(table)
24
+ end
25
+ end
26
+ private_class_method :format_table
27
+
28
+ def format_row(row)
29
+ row.font_style = :bold
30
+ row.align = :center
31
+ end
32
+ private_class_method :format_row
33
+
34
+ # :reek:FeatureEnvy
35
+ def format_columns(table)
36
+ table.columns(0..5).valign = :center
37
+ table.columns([2, 4, 5]).align = :right
38
+ table.column(3).align = :center
39
+ table_widths(table, table.width)
40
+ end
41
+ private_class_method :format_columns
42
+
43
+ # :reek:FeatureEnvy
44
+ def table_widths(table, width)
45
+ table.column(0).width = width * 0.16
46
+ table.columns([2, 3]).width = width * 0.13
47
+ table.column([4, 5]).width = width * 0.135
48
+ end
49
+ private_class_method :table_widths
50
+
51
+ PRODUCT_TABLE_BASE_DATA = [['CÓD.', 'DESCRIÇÃO', 'QTD.', 'UND.', 'V.UNIT', 'V.TOT']].freeze
52
+ def product_table_data(data)
53
+ data[:products].reduce(PRODUCT_TABLE_BASE_DATA) do |result, cur|
54
+ result + [[
55
+ cur[:code],
56
+ cur[:description],
57
+ format_number(BigDecimal(cur[:quantity])),
58
+ cur[:unit_label],
59
+ format_currency(BigDecimal(cur[:unit_value])),
60
+ format_currency(BigDecimal(cur[:total_value]))
61
+ ]]
62
+ end
63
+ end
64
+ private_class_method :product_table_data
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,110 @@
1
+ module BrInvoicesPdf
2
+ module Cfe
3
+ module Renderer
4
+ module QrCode
5
+ extend BaseRenderer
6
+
7
+ module_function
8
+
9
+ SAT_QRCODE_SEPARATOR = '|'.freeze
10
+ BARCODE_HEIGHT = 50
11
+
12
+ def execute(pdf, data)
13
+ render_box(pdf) do
14
+ options = pdf_options(page_paper_width(pdf.page.size), data)
15
+
16
+ generate_barcodes(pdf, options)
17
+ generate_qr_code(pdf, data, options)
18
+ end
19
+ end
20
+
21
+ # :reek:FeatureEnvy
22
+ def render_box(pdf)
23
+ box(pdf, [0, pdf.cursor], page_content_width(pdf)) do
24
+ pdf.text("Códigos de barra e QR Code\n\n", style: :italic)
25
+ yield
26
+ pdf.move_down(10)
27
+ end
28
+ end
29
+ private_class_method :render_box
30
+
31
+ def pdf_options(page_width, data)
32
+ qrcode_size = page_width * 0.65
33
+ barcodes_size = page_width * 0.85
34
+ access_key = data[:access_key][4..48]
35
+
36
+ { access_key: access_key, barcodes_size: barcodes_size, qrcode_size: qrcode_size, page_width: page_width }
37
+ end
38
+ private_class_method :pdf_options
39
+
40
+ # :reek:FeatureEnvy
41
+ def generate_qr_code(pdf, data, options)
42
+ qrcode_string = generate_qr_code_string(options[:access_key], data)
43
+ qrcode_size = options[:qrcode_size]
44
+ opts = {
45
+ at: [(options[:page_width] - qrcode_size) / 2, pdf.cursor],
46
+ width: qrcode_size,
47
+ height: qrcode_size
48
+ }
49
+ insert_image(pdf, generate_qr_code_data(qrcode_string, qrcode_size), opts)
50
+ end
51
+ private_class_method :generate_qr_code
52
+
53
+ def generate_qr_code_data(qr_code_string, qrcode_size)
54
+ qrcode = RQRCode::QRCode.new(qr_code_string)
55
+ blob = qrcode.as_png(size: qrcode_size, border_modules: 0).to_blob
56
+ StringIO.new(blob)
57
+ end
58
+ private_class_method :generate_qr_code_data
59
+
60
+ # rubocop:disable Metrics/AbcSize
61
+ def generate_qr_code_string(access_key, data)
62
+ sat_params = data[:sat_params]
63
+ access_key + SAT_QRCODE_SEPARATOR + sat_params[:emission_date] +
64
+ SAT_QRCODE_SEPARATOR + sat_params[:emission_hour] +
65
+ SAT_QRCODE_SEPARATOR + data[:payment][:total].delete('.') + SAT_QRCODE_SEPARATOR +
66
+ data[:company_attributes][:cnpj] + SAT_QRCODE_SEPARATOR +
67
+ sat_params[:document_qr_code_signature]
68
+ end
69
+ private_class_method :generate_qr_code_string
70
+
71
+ def generate_barcodes(pdf, pdf_options)
72
+ options = barcode_options(pdf, pdf_options[:page_width], pdf_options[:barcodes_size])
73
+ access_key = pdf_options[:access_key]
74
+ insert_image(pdf, generate_barcode_one(access_key), options)
75
+ insert_image(pdf, generate_barcode_two(access_key), options)
76
+ end
77
+ private_class_method :generate_barcodes
78
+
79
+ def insert_image(pdf, image, options)
80
+ pdf.image(image, options)
81
+ pdf.move_down(options[:height])
82
+ end
83
+ private_class_method :insert_image
84
+
85
+ def generate_barcode_one(access_key)
86
+ key = access_key[0..21]
87
+ blob = Barby::PngOutputter.new(Barby::Code128A.new(key)).to_png
88
+ StringIO.new(blob)
89
+ end
90
+ private_class_method :generate_barcode_one
91
+
92
+ def generate_barcode_two(access_key)
93
+ key = access_key[22..44]
94
+ blob = Barby::PngOutputter.new(Barby::Code128A.new(key)).to_png
95
+ StringIO.new(blob)
96
+ end
97
+ private_class_method :generate_barcode_two
98
+
99
+ def barcode_options(pdf, page_width, qrcode_size)
100
+ {
101
+ at: [(page_width - qrcode_size) / 2, pdf.cursor],
102
+ width: qrcode_size,
103
+ height: BARCODE_HEIGHT
104
+ }
105
+ end
106
+ private_class_method :barcode_options
107
+ end
108
+ end
109
+ end
110
+ end