ruby_danfe 0.9.0 → 1.11.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/README.md +122 -0
- data/Rakefile +28 -0
- data/lib/ruby_danfe.rb +1 -325
- data/lib/ruby_danfe/cst.rb +28 -0
- data/lib/ruby_danfe/dacte_generator.rb +511 -0
- data/lib/ruby_danfe/danfe_generator.rb +287 -0
- data/lib/ruby_danfe/danfe_nfce_generator.rb +237 -0
- data/lib/ruby_danfe/descricao.rb +44 -0
- data/lib/ruby_danfe/document.rb +80 -0
- data/lib/ruby_danfe/helper.rb +68 -0
- data/lib/ruby_danfe/options.rb +24 -0
- data/lib/ruby_danfe/railtie.rb +20 -0
- data/lib/ruby_danfe/ruby_danfe.rb +49 -0
- data/lib/ruby_danfe/version.rb +3 -0
- data/lib/ruby_danfe/xml.rb +58 -0
- data/ruby_danfe.gemspec +26 -11
- data/spec/features/ruby_danfe_spec.rb +59 -0
- data/spec/fixtures/4_decimals_nfe_simples_nacional.xml +1 -0
- data/spec/fixtures/cte.xml +1 -0
- data/spec/fixtures/nfe_date_format_infoadprod_infoadfisco_issues.xml +42 -0
- data/spec/fixtures/nfe_simples_nacional.xml +1 -0
- data/spec/fixtures/nfe_with_fci.xml +1 -0
- data/spec/fixtures/nfe_with_ns.xml +4 -0
- data/spec/fixtures/nfe_without_ns.xml +2 -0
- data/spec/lib/cst_spec.rb +51 -0
- data/spec/lib/descricao_spec.rb +129 -0
- data/spec/lib/helper_spec.rb +88 -0
- data/spec/lib/options_spec.rb +13 -0
- data/spec/lib/ruby_danfe_spec.rb +43 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/be_same_file_as.rb +9 -0
- data/test/danfe.xml +42 -0
- data/test/generate.rb +8 -0
- data/test/generate_nfce.rb +8 -0
- data/test/nfce.xml +147 -0
- data/test/nfe_simples_nacional.xml +1 -0
- data/test/nfe_with_fci.xml +1 -0
- data/test/nfe_with_ns.xml +4 -0
- data/test/nfe_without_ns.xml +2 -0
- metadata +167 -75
@@ -0,0 +1,44 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
module RubyDanfe
|
4
|
+
class Descricao
|
5
|
+
LINEBREAK = "\n"
|
6
|
+
|
7
|
+
def self.generate(det)
|
8
|
+
descricao = "#{det.css('prod/xProd').text}"
|
9
|
+
|
10
|
+
if need_infAdProd(det)
|
11
|
+
descricao += LINEBREAK
|
12
|
+
descricao += det.css('infAdProd').text
|
13
|
+
end
|
14
|
+
|
15
|
+
if need_fci(det)
|
16
|
+
descricao += LINEBREAK
|
17
|
+
descricao += "FCI: #{det.css('prod/nFCI').text}"
|
18
|
+
end
|
19
|
+
|
20
|
+
if need_st(det)
|
21
|
+
descricao += LINEBREAK
|
22
|
+
descricao += "ST: MVA: #{det.css('ICMS/*/pMVAST').text}% "
|
23
|
+
descricao += "* Alíq: #{det.css('ICMS/*/pICMSST').text}% "
|
24
|
+
descricao += "* BC: #{det.css('ICMS/*/vBCST').text} "
|
25
|
+
descricao += "* Vlr: #{det.css('ICMS/*/vICMSST').text}"
|
26
|
+
end
|
27
|
+
|
28
|
+
descricao
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def self.need_infAdProd(det)
|
33
|
+
!det.css('infAdProd').text.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.need_fci(det)
|
37
|
+
!det.css('prod/nFCI').text.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.need_st(det)
|
41
|
+
det.css('ICMS/*/vBCST').text.to_i > 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module RubyDanfe
|
2
|
+
class Document
|
3
|
+
def initialize
|
4
|
+
@document = Prawn::Document.new(
|
5
|
+
:page_size => 'A4',
|
6
|
+
:page_layout => :portrait,
|
7
|
+
:left_margin => 0,
|
8
|
+
:right_margin => 0,
|
9
|
+
:top_margin => 0,
|
10
|
+
:botton_margin => 0
|
11
|
+
)
|
12
|
+
|
13
|
+
@document.font "Times-Roman"
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(method_name, *args, &block)
|
17
|
+
@document.send(method_name, *args, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to_missing?(method_name, include_private = false)
|
21
|
+
@document.respond_to?(method_name, include_private) || super
|
22
|
+
end
|
23
|
+
|
24
|
+
def ititle(h, w, x, y, title)
|
25
|
+
self.text_box title, :size => 10, :at => [x.cm, Helper.invert(y.cm) - 2], :width => w.cm, :height => h.cm, :style => :bold
|
26
|
+
end
|
27
|
+
|
28
|
+
def ibarcode(h, w, x, y, info)
|
29
|
+
Barby::Code128C.new(info).annotate_pdf(self, :x => x.cm, :y => Helper.invert(y.cm), :width => w.cm, :height => h.cm) if info != ''
|
30
|
+
end
|
31
|
+
|
32
|
+
def iqrcode(h, w, x, y, info)
|
33
|
+
Barby::QrCode.new(info, :level => :q).annotate_pdf(self, :x => x.cm, :y => Helper.invert(y.cm), :width => w.cm, :height => h.cm) if info != ''
|
34
|
+
end
|
35
|
+
|
36
|
+
def irectangle(h, w, x, y)
|
37
|
+
self.stroke_rectangle [x.cm, Helper.invert(y.cm)], w.cm, h.cm
|
38
|
+
end
|
39
|
+
|
40
|
+
def ibox(h, w, x, y, title = '', info = '', options = {})
|
41
|
+
box [x.cm, Helper.invert(y.cm)], w.cm, h.cm, title, info, options
|
42
|
+
end
|
43
|
+
|
44
|
+
def idate(h, w, x, y, title = '', info = '', options = {})
|
45
|
+
tt = info.gsub(/T.*/, '').split('-')
|
46
|
+
ibox h, w, x, y, title, "#{tt[2]}/#{tt[1]}/#{tt[0]}", options
|
47
|
+
end
|
48
|
+
|
49
|
+
def box(at, w, h, title = '', info = '', options = {})
|
50
|
+
options = {
|
51
|
+
:align => :left,
|
52
|
+
:size => 10,
|
53
|
+
:style => nil,
|
54
|
+
:valign => :top,
|
55
|
+
:border => 1
|
56
|
+
}.merge(options)
|
57
|
+
self.stroke_rectangle at, w, h if options[:border] == 1
|
58
|
+
self.text_box title, :size => 6, :style => :italic, :at => [at[0] + 2, at[1] - 2], :width => w - 4, :height => 8 if title != ''
|
59
|
+
self.text_box info, :size => options[:size], :at => [at[0] + 2, at[1] - (title != '' ? 14 : 4) ], :width => w - 4, :height => h - (title != '' ? 14 : 4), :align => options[:align], :style => options[:style], :valign => options[:valign]
|
60
|
+
end
|
61
|
+
|
62
|
+
def inumeric(h, w, x, y, title = '', info = '', options = {})
|
63
|
+
numeric [x.cm, Helper.invert(y.cm)], w.cm, h.cm, title, info, options
|
64
|
+
end
|
65
|
+
|
66
|
+
def numeric(at, w, h, title = '', info = '', options = {})
|
67
|
+
options = {:decimals => 2}.merge(options)
|
68
|
+
info = Helper.numerify(info, options[:decimals]) if info != ''
|
69
|
+
box at, w, h, title, info, options.merge({:align => :right})
|
70
|
+
end
|
71
|
+
|
72
|
+
def itable(h, w, x, y, data, options = {}, &block)
|
73
|
+
self.bounding_box [x.cm, Helper.invert(y.cm)], :width => w.cm, :height => h.cm do
|
74
|
+
self.table data, options do |table|
|
75
|
+
yield(table)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module RubyDanfe
|
2
|
+
class Helper
|
3
|
+
def self.numerify(number, decimals = 2)
|
4
|
+
number = number.tr("\n","").delete(" ")
|
5
|
+
return "" if !number || number == ""
|
6
|
+
int, frac = ("%.#{decimals}f" % number).split(".")
|
7
|
+
int.gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1\.")
|
8
|
+
int + "," + frac
|
9
|
+
rescue
|
10
|
+
number
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.invert(y)
|
14
|
+
28.7.cm - y
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.format_quantity(qty)
|
18
|
+
return Helper.numerify(qty, RubyDanfe.options.quantity_decimals) if RubyDanfe.options.numerify_prod_qcom
|
19
|
+
qty.gsub!(",", ".")
|
20
|
+
qty[qty.rindex('.')] = ',' if qty.rindex('.')
|
21
|
+
qty
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.format_datetime(string)
|
25
|
+
formated_datetime = ""
|
26
|
+
|
27
|
+
if not string.empty?
|
28
|
+
date = DateTime.strptime(string, "%Y-%m-%dT%H:%M:%S")
|
29
|
+
formated_datetime = date.strftime("%d/%m/%Y %H:%M:%S")
|
30
|
+
end
|
31
|
+
|
32
|
+
formated_datetime
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.format_date(string)
|
36
|
+
formated_date = ""
|
37
|
+
|
38
|
+
if not string.empty?
|
39
|
+
date = Date.strptime(string, "%Y-%m-%d")
|
40
|
+
formated_date = date.strftime("%d/%m/%Y")
|
41
|
+
end
|
42
|
+
|
43
|
+
formated_date
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.format_time(string)
|
47
|
+
formated_time = ""
|
48
|
+
|
49
|
+
if not string.empty?
|
50
|
+
time = Time.strptime(string, "%H:%M:%S")
|
51
|
+
formated_time = time.strftime("%H:%M:%S")
|
52
|
+
end
|
53
|
+
|
54
|
+
formated_time
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.extract_time_from_date(string)
|
58
|
+
formated_time = ""
|
59
|
+
|
60
|
+
if not string.empty?
|
61
|
+
date = DateTime.strptime(string, "%Y-%m-%dT%H:%M:%S")
|
62
|
+
formated_time = date.strftime("%H:%M:%S")
|
63
|
+
end
|
64
|
+
|
65
|
+
formated_time
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RubyDanfe
|
2
|
+
class Options < OpenStruct
|
3
|
+
|
4
|
+
DEFAULTOPTIONS = {
|
5
|
+
quantity_decimals: 2,
|
6
|
+
numerify_prod_qcom: false
|
7
|
+
}
|
8
|
+
|
9
|
+
def initialize(new_options={})
|
10
|
+
options = DEFAULTOPTIONS.merge(config_yaml_load)
|
11
|
+
super options.merge(new_options)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def file
|
16
|
+
File.exists?("config/ruby_danfe.yml") ? File.open("config/ruby_danfe.yml").read : ""
|
17
|
+
end
|
18
|
+
|
19
|
+
def config_yaml_load
|
20
|
+
@file_read = YAML.load(file)
|
21
|
+
@file_read ? (@file_read["ruby_danfe"]||{})["options"] : {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "prawn"
|
2
|
+
require "prawn/measurement_extensions"
|
3
|
+
require "barby"
|
4
|
+
require "barby/barcode/code_128"
|
5
|
+
require "barby/outputter/prawn_outputter"
|
6
|
+
require "nokogiri"
|
7
|
+
require 'ostruct'
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
require_relative '../ruby_danfe.rb'
|
11
|
+
require_relative 'options.rb'
|
12
|
+
require_relative 'helper.rb'
|
13
|
+
require_relative 'cst.rb'
|
14
|
+
require_relative 'document.rb'
|
15
|
+
require_relative 'version.rb'
|
16
|
+
require_relative 'dacte_generator.rb'
|
17
|
+
require_relative 'danfe_generator.rb'
|
18
|
+
require_relative 'xml.rb'
|
19
|
+
require_relative 'descricao.rb'
|
20
|
+
require_relative 'ruby_danfe.rb'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding:utf-8
|
2
|
+
module RubyDanfe
|
3
|
+
def self.generate(pdf_filename, xml_filename, type = :danfe, new_options = {})
|
4
|
+
self.options = new_options if !new_options.empty?
|
5
|
+
|
6
|
+
xml_string = File.new(xml_filename)
|
7
|
+
render_file(pdf_filename, xml_string, type)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.render(xml_string, type = :danfe, new_options = {})
|
11
|
+
self.options = new_options if !new_options.empty?
|
12
|
+
|
13
|
+
pdf = generatePDF(xml_string, type)
|
14
|
+
pdf.render
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.render_file(pdf_filename, xml_string, type = :danfe, new_options = {})
|
18
|
+
self.options = new_options if !new_options.empty?
|
19
|
+
|
20
|
+
pdf = generatePDF(xml_string, type)
|
21
|
+
pdf.render_file pdf_filename
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.options
|
25
|
+
@options ||= RubyDanfe::Options.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.options=(new_options = {})
|
29
|
+
@options = RubyDanfe::Options.new(new_options)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def self.generatePDF(xml_string, type = :danfe, new_options = {})
|
34
|
+
self.options = new_options if !new_options.empty?
|
35
|
+
|
36
|
+
xml = XML.new(xml_string)
|
37
|
+
|
38
|
+
if type == :danfe
|
39
|
+
generator = DanfeGenerator.new(xml)
|
40
|
+
elsif type == :danfe_nfce
|
41
|
+
generator = DanfeNfceGenerator.new(xml)
|
42
|
+
else
|
43
|
+
generator = DacteGenerator.new(xml)
|
44
|
+
end
|
45
|
+
|
46
|
+
pdf = generator.generatePDF
|
47
|
+
pdf
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RubyDanfe
|
2
|
+
class XML
|
3
|
+
def css(xpath)
|
4
|
+
@xml.css(xpath)
|
5
|
+
end
|
6
|
+
|
7
|
+
def xpath(regex)
|
8
|
+
doc = Nokogiri::HTML(@xml.to_s)
|
9
|
+
return doc.xpath(regex)
|
10
|
+
end
|
11
|
+
|
12
|
+
def regex_string(search_string, regex)
|
13
|
+
doc = Nokogiri::HTML(search_string)
|
14
|
+
return doc.xpath(regex)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(xml)
|
18
|
+
@xml = Nokogiri::XML(xml)
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](xpath)
|
22
|
+
node = @xml.css(xpath)
|
23
|
+
return node ? node.text : ""
|
24
|
+
end
|
25
|
+
|
26
|
+
def render
|
27
|
+
if @xml.at_css('infNFe/ide')
|
28
|
+
RubyDanfe.render @xml.to_s, :danfe
|
29
|
+
else
|
30
|
+
RubyDanfe.render @xml.to_s, :dacte
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def collect(ns, tag, &block)
|
35
|
+
result = []
|
36
|
+
# Tenta primeiro com uso de namespace
|
37
|
+
begin
|
38
|
+
@xml.xpath("//#{ns}:#{tag}").each do |det|
|
39
|
+
result << yield(det)
|
40
|
+
end
|
41
|
+
rescue
|
42
|
+
# Caso dê erro, tenta sem
|
43
|
+
@xml.xpath("//#{tag}").each do |det|
|
44
|
+
result << yield(det)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def attrib(node, attrib)
|
51
|
+
begin
|
52
|
+
return @xml.css(node).attr(attrib).text
|
53
|
+
rescue
|
54
|
+
""
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/ruby_danfe.gemspec
CHANGED
@@ -1,12 +1,27 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "ruby_danfe/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "ruby_danfe"
|
7
|
+
spec.version = RubyDanfe::VERSION
|
8
|
+
spec.summary = "DANFE and DACTE pdf generator for Brazilian invoices and transportation docs."
|
9
|
+
spec.author = "Eduardo Reboucas"
|
10
|
+
spec.email = "eduardo.reboucas@gmail.com"
|
11
|
+
spec.homepage = "http://github.com/taxweb/ruby_danfe"
|
12
|
+
spec.license = "MIT"
|
13
|
+
|
14
|
+
spec.files = `git ls-files`.split("\n")
|
15
|
+
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_dependency "nokogiri"
|
20
|
+
spec.add_dependency "prawn", '~> 1.0.0'
|
21
|
+
spec.add_dependency "barby"
|
22
|
+
spec.add_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_development_dependency "pry"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "simplecov"
|
12
27
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "RubyDanfe generated pdf files" do
|
4
|
+
let(:base_dir) { "./spec/fixtures/"}
|
5
|
+
let(:output_pdf) { "#{base_dir}output.pdf" }
|
6
|
+
|
7
|
+
before {
|
8
|
+
File.delete(output_pdf) if File.exist?(output_pdf)
|
9
|
+
RubyDanfe.options = {"quantity_decimals" => 2}
|
10
|
+
}
|
11
|
+
|
12
|
+
it "renders a basic NF-e with namespace" do
|
13
|
+
expect(File.exist?(output_pdf)).to be_false
|
14
|
+
|
15
|
+
RubyDanfe.generate(output_pdf, "#{base_dir}nfe_with_ns.xml")
|
16
|
+
|
17
|
+
expect("#{base_dir}nfe_with_ns.xml.fixture.pdf").to be_same_file_as(output_pdf)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "renders another basic NF-e without namespace" do
|
21
|
+
expect(File.exist?(output_pdf)).to be_false
|
22
|
+
|
23
|
+
RubyDanfe.generate(output_pdf, "#{base_dir}nfe_without_ns.xml")
|
24
|
+
|
25
|
+
expect("#{base_dir}nfe_without_ns.xml.fixture.pdf").to be_same_file_as(output_pdf)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "renders a NF-e having FCI in its items" do
|
29
|
+
expect(File.exist?(output_pdf)).to be_false
|
30
|
+
|
31
|
+
RubyDanfe.generate(output_pdf, "#{base_dir}nfe_with_fci.xml")
|
32
|
+
|
33
|
+
expect("#{base_dir}nfe_with_fci.xml.fixture.pdf").to be_same_file_as(output_pdf)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "renders a NF-e of Simples Nacional using CSOSN" do
|
37
|
+
expect(File.exist?(output_pdf)).to be_false
|
38
|
+
|
39
|
+
RubyDanfe.generate(output_pdf, "#{base_dir}nfe_simples_nacional.xml")
|
40
|
+
|
41
|
+
expect("#{base_dir}nfe_simples_nacional.xml.fixture.pdf").to be_same_file_as(output_pdf)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "renders a basic CT-e" do
|
45
|
+
expect(File.exist?(output_pdf)).to be_false
|
46
|
+
|
47
|
+
RubyDanfe.generate(output_pdf, "#{base_dir}cte.xml", :dacte)
|
48
|
+
|
49
|
+
expect("#{base_dir}cte.xml.fixture.pdf").to be_same_file_as(output_pdf)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "renders quantity field with 4 decimals" do
|
53
|
+
expect(File.exist?(output_pdf)).to be_false
|
54
|
+
|
55
|
+
RubyDanfe.generate(output_pdf, "#{base_dir}4_decimals_nfe_simples_nacional.xml", :danfe, {"quantity_decimals" => 4})
|
56
|
+
|
57
|
+
expect("#{base_dir}4_decimals_nfe_simples_nacional.xml.fixture.pdf").to be_same_file_as(output_pdf)
|
58
|
+
end
|
59
|
+
end
|