facturae_print 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in facturae_print.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Álvaro Bautista
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # FacturaePrint
2
+
3
+ FacturaePrint translates a facturae xml file into an understandable HTML or PDF file.
4
+
5
+ The facturae format specification is available on [http://www.facturae.es](http://www.facturae.es). There is also an english version available [here](http://www.facturae.es/es-ES/Documentacion/EsquemaFormato/Esquema%20Formato/Versi%C3%B3n%203_2/Esquema_ingles_v3_2.pdf).
6
+
7
+ ### Installation
8
+
9
+ gem install facturae_print
10
+
11
+ ### Usage
12
+
13
+ FacturaePrint uses [erubis](http://www.kuwata-lab.com/erubis) to translate the facturae xml file into an html file. It also can generate a pdf version from the generated html file, using [PDFKit](http://github.com/jdpace/PDFKit). Nothing fancy.
14
+
15
+ Right. Let's see some examples:
16
+
17
+ facturae_print html 022011.xml templates/default.eruby -o test.html
18
+
19
+ facturae_print pdf 022011.xml templates/default.eruby -s templates/default.css -o test.pdf
20
+
21
+ That's it. What else did you expect?
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task :default => :spec
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+
5
+ require 'facturae_print'
6
+ require 'facturae_print/cli'
7
+
8
+ FacturaePrint::CLI.start
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "facturae_print/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "facturae_print"
7
+ s.version = FacturaePrint::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Alvaro Bautista"]
10
+ s.email = ["alvarobp@gmail.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{facturae_print translates a facturae xml file into an understandable HTML or PDF file}
13
+ s.description = %q{facturae_print translates a facturae xml file into an understandable HTML or PDF file}
14
+
15
+ s.add_dependency("nokogiri", ">= 1.4.4")
16
+ s.add_dependency("erubis", ">= 2.6.6")
17
+ s.add_dependency("wkhtmltopdf-binary", ">= 0.9.5.1")
18
+ s.add_dependency("pdfkit", ">= 0.5.0")
19
+ s.add_dependency("thor", ">= 0.14.6")
20
+
21
+ s.add_development_dependency("rspec", "~> 2.4.0")
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,6 @@
1
+ require 'facturae_print/xml_objectifier'
2
+ require 'facturae_print/invoice'
3
+ require 'facturae_print/renderers/html_renderer_helpers'
4
+ require 'facturae_print/renderers/html_renderer'
5
+ require 'facturae_print/renderers/pdf_renderer'
6
+ require 'facturae_print/core_ext/string'
@@ -0,0 +1,42 @@
1
+ require 'thor'
2
+
3
+ module FacturaePrint
4
+ class CLI < Thor
5
+
6
+ desc "html facturae_xml eruby_template", "Generates an html version for a facturae xml"
7
+ method_option :output, :type => :string, :aliases => "-o", :banner => "Output to a file instead of into stdio"
8
+ def html(facturae_xml, eruby_template)
9
+ invoice = FacturaePrint::Invoice.new(File.read(facturae_xml))
10
+ template = File.read(eruby_template)
11
+
12
+ html_renderer = FacturaePrint::Renderers::HTMLRenderer.new(template, invoice)
13
+ rendered_html = html_renderer.render
14
+
15
+ if options[:output]
16
+ File.open(options[:output], 'w') { |f| f.write(rendered_html) }
17
+ else
18
+ shell.say(rendered_html)
19
+ end
20
+ end
21
+
22
+ desc "pdf facturae_xml eruby_template", "Generates a pdf version for a facturae xml"
23
+ method_option :stylesheet, :type => :string, :aliases => "-s", :banner => "Use the given CSS stylesheet on rendering"
24
+ method_option :output, :type => :string, :aliases => "-o", :banner => "Output to a file instead of into stdio"
25
+ def pdf(facturae_xml, eruby_template)
26
+ invoice = FacturaePrint::Invoice.new(File.read(facturae_xml))
27
+ template = File.read(eruby_template)
28
+ renderer_options = {}
29
+ renderer_options[:stylesheet] = File.read(options[:stylesheet]) if options[:stylesheet]
30
+
31
+ pdf_renderer = FacturaePrint::Renderers::PDFRenderer.new(template, invoice, renderer_options)
32
+ rendered_pdf = pdf_renderer.render
33
+
34
+ if options[:output]
35
+ File.open(options[:output], 'w') { |f| f.write(rendered_pdf) }
36
+ else
37
+ shell.say(rendered_pdf)
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+ class String
2
+
3
+ unless method_defined?(:underscore)
4
+ define_method :underscore do
5
+ word = self.dup
6
+ word.gsub!(/::/, '/')
7
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
8
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
9
+ word.tr!("-", "_")
10
+ word.downcase!
11
+ word
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,19 @@
1
+ require 'ostruct'
2
+ require 'nokogiri'
3
+
4
+ module FacturaePrint
5
+
6
+ class Invoice < OpenStruct
7
+
8
+ def initialize(xml_file)
9
+ super(nil)
10
+ xml = Nokogiri::XML(xml_file)
11
+
12
+ FacturaePrint::XMLObjectifier.build(xml.root,
13
+ :object => self,
14
+ :collection_nodes => ["Invoices", "TaxesOutputs", "Items"])
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,24 @@
1
+ require 'erubis'
2
+
3
+ module FacturaePrint
4
+ module Renderers
5
+ class HTMLRenderer
6
+
7
+ def initialize(template, invoice)
8
+ @template = template
9
+ @invoice = invoice
10
+ end
11
+
12
+ def render
13
+ eruby = Erubis::Eruby.new(@template)
14
+ __context = Erubis::Context.new
15
+ class << __context
16
+ include FacturaePrint::Renderers::HTMLRendererHelpers
17
+ end
18
+ __context[:invoice] = @invoice
19
+ eruby.evaluate(__context)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module FacturaePrint
2
+ module Renderers
3
+ module HTMLRendererHelpers
4
+
5
+ def to_currency(amount)
6
+ @invoice_currency_symbol ||= currency_symbol(@invoice.invoices.first.invoice_issue_data.invoice_currency_code)
7
+ "#{amount} #{@invoice_currency_symbol}"
8
+ end
9
+
10
+ def currency_symbol(currency_code)
11
+ case currency_code
12
+ when "EUR": "€"
13
+ when "USD": "$"
14
+ else
15
+ currency_code
16
+ end
17
+ end
18
+
19
+ def individual_full_name(individual)
20
+ [individual.name, individual.first_surname, individual.second_surname].compact.join(' ')
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ require 'pdfkit'
2
+
3
+ module FacturaePrint
4
+ module Renderers
5
+ class PDFRenderer
6
+
7
+ def initialize(template, invoice, args={})
8
+ @template = template
9
+ @invoice = invoice
10
+ @stylesheet = args.delete(:stylesheet)
11
+ @pdfkit_options = args
12
+ end
13
+
14
+ def render
15
+ html = FacturaePrint::Renderers::HTMLRenderer.new(@template, @invoice).render
16
+
17
+ append_stylesheet_tag(html, (@stylesheet.is_a?(IO) ? @stylesheet.read : @stylesheet)) if @stylesheet
18
+ PDFKit.new(html, @pdfkit_options).to_pdf
19
+ end
20
+
21
+ private
22
+
23
+ def append_stylesheet_tag(html_string, stylesheet)
24
+ if html_string.match(/<\/head>/)
25
+ html_string.gsub!(/(<\/head>)/, "<style>#{stylesheet}</style>"+'\1')
26
+ else
27
+ html_string.insert(0, "<style>#{stylesheet}</style>")
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module FacturaePrint
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,34 @@
1
+ module FacturaePrint
2
+ module XMLObjectifier
3
+
4
+ def self.build(node, options={})
5
+ options = {:collection_nodes => []}.merge(options)
6
+ return node.text if node.text?
7
+
8
+ children = get_node_children(node)
9
+ return nil if children.empty? && node.content.empty?
10
+
11
+ if children.size == 1 && children.first.text?
12
+ children.first.text
13
+ else
14
+ object = options.delete(:object) || OpenStruct.new
15
+ children.each do |child|
16
+ value = if options[:collection_nodes].include?(child.name)
17
+ get_node_children(child).map{|c| build(c, options)}
18
+ else
19
+ build(child, options)
20
+ end
21
+
22
+ object.send("#{child.name.underscore}=", value)
23
+ end
24
+ object
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def self.get_node_children(node)
31
+ node.children.reject { |c| c.blank? }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,144 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <fe:Facturae xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:fe="http://www.facturae.es/Facturae/2007/v3.1/Facturae">
3
+ <FileHeader>
4
+ <SchemaVersion>3.1</SchemaVersion>
5
+ <Modality>I</Modality>
6
+ <InvoiceIssuerType>RE</InvoiceIssuerType>
7
+ <Batch>
8
+ <BatchIdentifier>12345678A</BatchIdentifier>
9
+ <InvoicesCount>1</InvoicesCount>
10
+ <TotalInvoicesAmount>
11
+ <TotalAmount>637.50</TotalAmount>
12
+ </TotalInvoicesAmount>
13
+ <TotalOutstandingAmount>
14
+ <TotalAmount>637.50</TotalAmount>
15
+ </TotalOutstandingAmount>
16
+ <TotalExecutableAmount>
17
+ <TotalAmount>637.50</TotalAmount>
18
+ </TotalExecutableAmount>
19
+ <InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
20
+ </Batch>
21
+ </FileHeader>
22
+ <Parties>
23
+ <SellerParty>
24
+ <TaxIdentification>
25
+ <PersonTypeCode>F</PersonTypeCode>
26
+ <ResidenceTypeCode>E</ResidenceTypeCode>
27
+ <TaxIdentificationNumber>12345678A</TaxIdentificationNumber>
28
+ </TaxIdentification>
29
+ <Individual>
30
+ <Name>John</Name>
31
+ <FirstSurname>Developer</FirstSurname>
32
+ <SecondSurname/>
33
+ <AddressInSpain>
34
+ <Address>Colón 14</Address>
35
+ <PostCode>28004</PostCode>
36
+ <Town>Madrid</Town>
37
+ <Province>Madrid</Province>
38
+ <CountryCode>ESP</CountryCode>
39
+ </AddressInSpain>
40
+ <ContactDetails>
41
+ <Telephone>98765432</Telephone>
42
+ <ElectronicMail>john@developer.com</ElectronicMail>
43
+ </ContactDetails>
44
+ </Individual>
45
+ </SellerParty>
46
+ <BuyerParty>
47
+ <TaxIdentification>
48
+ <PersonTypeCode>J</PersonTypeCode>
49
+ <ResidenceTypeCode>E</ResidenceTypeCode>
50
+ <TaxIdentificationNumber>Z12345678</TaxIdentificationNumber>
51
+ </TaxIdentification>
52
+ <LegalEntity>
53
+ <CorporateName>Biggest Company</CorporateName>
54
+ <AddressInSpain>
55
+ <Address>Gran Vía 28</Address>
56
+ <PostCode>28013</PostCode>
57
+ <Town>Madrid</Town>
58
+ <Province>Madrid</Province>
59
+ <CountryCode>ESP</CountryCode>
60
+ </AddressInSpain>
61
+ <ContactDetails>
62
+ <Telephone>654321987</Telephone>
63
+ <ElectronicMail>biggest@company.es</ElectronicMail>
64
+ </ContactDetails>
65
+ </LegalEntity>
66
+ </BuyerParty>
67
+ </Parties>
68
+ <Invoices>
69
+ <Invoice>
70
+ <InvoiceHeader>
71
+ <InvoiceNumber>1</InvoiceNumber>
72
+ <InvoiceDocumentType>FC</InvoiceDocumentType>
73
+ <InvoiceClass>OO</InvoiceClass>
74
+ </InvoiceHeader>
75
+ <InvoiceIssueData>
76
+ <IssueDate>2011-01-31</IssueDate>
77
+ <InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
78
+ <TaxCurrencyCode>EUR</TaxCurrencyCode>
79
+ <LanguageName>es</LanguageName>
80
+ </InvoiceIssueData>
81
+ <TaxesOutputs>
82
+ <Tax>
83
+ <TaxTypeCode>04</TaxTypeCode>
84
+ <TaxRate>-15.00</TaxRate>
85
+ <TaxableBase>
86
+ <TotalAmount>750.00</TotalAmount>
87
+ </TaxableBase>
88
+ <TaxAmount>
89
+ <TotalAmount>-112.50</TotalAmount>
90
+ </TaxAmount>
91
+ </Tax>
92
+ </TaxesOutputs>
93
+ <InvoiceTotals>
94
+ <TotalGrossAmount>750.00</TotalGrossAmount>
95
+ <TotalGrossAmountBeforeTaxes>750.00</TotalGrossAmountBeforeTaxes>
96
+ <TotalTaxOutputs>-112.50</TotalTaxOutputs>
97
+ <TotalTaxesWithheld>0.00</TotalTaxesWithheld>
98
+ <InvoiceTotal>637.50</InvoiceTotal>
99
+ <TotalOutstandingAmount>637.50</TotalOutstandingAmount>
100
+ <TotalExecutableAmount>637.50</TotalExecutableAmount>
101
+ </InvoiceTotals>
102
+ <Items>
103
+ <InvoiceLine>
104
+ <ItemDescription>Working too hard</ItemDescription>
105
+ <Quantity>1.00</Quantity>
106
+ <UnitPriceWithoutTax>700.000000</UnitPriceWithoutTax>
107
+ <TotalCost>700.00</TotalCost>
108
+ <GrossAmount>700.00</GrossAmount>
109
+ <TaxesOutputs>
110
+ <Tax>
111
+ <TaxTypeCode>04</TaxTypeCode>
112
+ <TaxRate>-15.00</TaxRate>
113
+ <TaxableBase>
114
+ <TotalAmount>700.00</TotalAmount>
115
+ </TaxableBase>
116
+ <TaxAmount>
117
+ <TotalAmount>-105.00</TotalAmount>
118
+ </TaxAmount>
119
+ </Tax>
120
+ </TaxesOutputs>
121
+ </InvoiceLine>
122
+ <InvoiceLine>
123
+ <ItemDescription>Working not as hard as before</ItemDescription>
124
+ <Quantity>1.00</Quantity>
125
+ <UnitPriceWithoutTax>50.000000</UnitPriceWithoutTax>
126
+ <TotalCost>50.00</TotalCost>
127
+ <GrossAmount>50.00</GrossAmount>
128
+ <TaxesOutputs>
129
+ <Tax>
130
+ <TaxTypeCode>04</TaxTypeCode>
131
+ <TaxRate>-15.00</TaxRate>
132
+ <TaxableBase>
133
+ <TotalAmount>50.00</TotalAmount>
134
+ </TaxableBase>
135
+ <TaxAmount>
136
+ <TotalAmount>-7.50</TotalAmount>
137
+ </TaxAmount>
138
+ </Tax>
139
+ </TaxesOutputs>
140
+ </InvoiceLine>
141
+ </Items>
142
+ </Invoice>
143
+ </Invoices>
144
+ </fe:Facturae>
@@ -0,0 +1,28 @@
1
+ require "spec_helper"
2
+
3
+ describe FacturaePrint::Invoice do
4
+
5
+ before(:each) do
6
+ @invoice = FacturaePrint::Invoice.new(fixture('facturae.xml'))
7
+ end
8
+
9
+ it "should instantiate as an OpenStruct mapping fe:Facturae node children" do
10
+ @invoice.file_header.schema_version = "3.1"
11
+ @invoice.file_header.batch.batch_identifier = "12345678A"
12
+ @invoice.parties.seller_party.individual.name.should == "John"
13
+ @invoice.parties.seller_party.individual.first_surname.should == "Developer"
14
+ end
15
+
16
+ it "should build the proper collections" do
17
+ # Collections: Invoices, TaxesOutputs, Items
18
+ @invoice.invoices.should be_a(Array)
19
+ @invoice.invoices.size.should == 1
20
+ @invoice.invoices.first.taxes_outputs.should be_a(Array)
21
+ @invoice.invoices.first.taxes_outputs.first.tax_rate.should == "-15.00"
22
+ @invoice.invoices.first.items.should be_a(Array)
23
+ @invoice.invoices.first.items.size.should == 2
24
+ @invoice.invoices.first.items[0].item_description = "Working too hard"
25
+ @invoice.invoices.first.items[1].item_description = "Working not as hard as before"
26
+ end
27
+
28
+ end
@@ -0,0 +1,19 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe FacturaePrint::Renderers::HTMLRenderer do
4
+
5
+ it "should render the given template with the given invoice object as @invoice instance variable" do
6
+ templ = "Example with <%= @invoice.name %>"
7
+ fake_invoice = OpenStruct.new("name" => "My first invoice ever")
8
+ html_renderer = FacturaePrint::Renderers::HTMLRenderer.new(templ, fake_invoice)
9
+ html_renderer.render.should == "Example with My first invoice ever"
10
+ end
11
+
12
+ it "should make FacturaePrint::Renderers::HTMLRendererHelpers methods available" do
13
+ templ = "Example with <%= @invoice.name %> for a value of 6000 <%= currency_code('USD') %>"
14
+ fake_invoice = OpenStruct.new("name" => "My first invoice ever")
15
+ html_renderer = FacturaePrint::Renderers::HTMLRenderer.new(templ, fake_invoice)
16
+ "Example with My first invoice ever for a value of 6000 $"
17
+ end
18
+
19
+ end
@@ -0,0 +1,55 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe FacturaePrint::Renderers::PDFRenderer do
4
+
5
+ before(:each) do
6
+ @invoice = OpenStruct.new("name" => "My first invoice ever")
7
+ end
8
+
9
+ it "should render the given template using HTMLRenderer and use it to render with PDFKit" do
10
+ templ = "Example with <%= @invoice.name %>"
11
+
12
+ pdf_renderer = FacturaePrint::Renderers::PDFRenderer.new(templ, @invoice)
13
+ kit_mock = mock()
14
+ kit_mock.stub(:to_pdf).and_return("PDF RESULT")
15
+
16
+ PDFKit.should_receive(:new).with("Example with My first invoice ever", anything()).and_return(kit_mock)
17
+ pdf_renderer.render.should == "PDF RESULT"
18
+ end
19
+
20
+ it "should append stylesheet string passed in the stylesheet option" do
21
+ templ = "Example with <%= @invoice.name %>"
22
+ css = "* { border:1px solid red; }"
23
+
24
+ pdf_renderer = FacturaePrint::Renderers::PDFRenderer.new(templ, @invoice, :stylesheet => css)
25
+ kit_mock = mock()
26
+ kit_mock.stub(:to_pdf).and_return("PDF RESULT")
27
+
28
+ PDFKit.should_receive(:new).with("<style>#{css}</style>Example with My first invoice ever", anything()).and_return(kit_mock)
29
+ pdf_renderer.render.should == "PDF RESULT"
30
+ end
31
+
32
+ it "should append stylesheet string passed in the stylesheet option to the head section if there is one" do
33
+ templ = "<html><head><title>Invoice</title></head><body>Example with <%= @invoice.name %></body></html>"
34
+ css = "* { border:1px solid red; }"
35
+
36
+ pdf_renderer = FacturaePrint::Renderers::PDFRenderer.new(templ, @invoice, :stylesheet => css)
37
+ kit_mock = mock()
38
+ kit_mock.stub(:to_pdf).and_return("PDF RESULT")
39
+
40
+ PDFKit.should_receive(:new).with("<html><head><title>Invoice</title><style>#{css}</style></head><body>Example with My first invoice ever</body></html>", anything()).and_return(kit_mock)
41
+ pdf_renderer.render.should == "PDF RESULT"
42
+ end
43
+
44
+ it "should pass given options to PDFKit" do
45
+ templ = "Example with <%= @invoice.name %>"
46
+
47
+ pdf_renderer = FacturaePrint::Renderers::PDFRenderer.new(templ, @invoice, :page_size => "A4")
48
+ kit_mock = mock()
49
+ kit_mock.stub(:to_pdf).and_return("PDF RESULT")
50
+
51
+ PDFKit.should_receive(:new).with("Example with My first invoice ever", {:page_size => "A4"}).and_return(kit_mock)
52
+ pdf_renderer.render.should == "PDF RESULT"
53
+ end
54
+
55
+ end
@@ -0,0 +1,27 @@
1
+ $:.unshift File.expand_path('..', __FILE__)
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+
4
+ require 'rubygems'
5
+ require 'facturae_print'
6
+
7
+ RSpec.configure do |config|
8
+ # == Mock Framework
9
+ #
10
+ # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
11
+ #
12
+ # config.mock_with :mocha
13
+ # config.mock_with :flexmock
14
+ # config.mock_with :rr
15
+ config.mock_with :rspec
16
+ end
17
+
18
+ def fixture(name)
19
+ if @fixtures.nil?
20
+ @fixtures ||= {}
21
+ Dir["#{File.expand_path('../fixtures', __FILE__)}/*"].each do |file|
22
+ pathname = Pathname.new(file)
23
+ @fixtures[pathname.basename.to_s] = pathname.read
24
+ end
25
+ end
26
+ @fixtures[name]
27
+ end
@@ -0,0 +1,108 @@
1
+ require "spec_helper"
2
+
3
+ describe FacturaePrint::XMLObjectifier do
4
+
5
+ it "should build from one node xml" do
6
+ xml = "<lonely>1</lonely>"
7
+
8
+ object = FacturaePrint::XMLObjectifier.build(Nokogiri::XML(xml))
9
+ object.should be_a(OpenStruct)
10
+ object.lonely.should == "1"
11
+ end
12
+
13
+ it "should build from xml with nested nodes" do
14
+ xml = <<-XML
15
+ <zoo>
16
+ <tiger>
17
+ <name>Shere Khan</name>
18
+ </tiger>
19
+ <lion>
20
+ <name>Mufasa</name>
21
+ <dimensions>
22
+ <height>
23
+ <unit>inches</unit>
24
+ <value>30</value>
25
+ </height>
26
+ <length>
27
+ <unit>meters</unit>
28
+ <value>2</value>
29
+ </length>
30
+ </dimensions>
31
+ </lion>
32
+ </zoo>
33
+ XML
34
+
35
+ object = FacturaePrint::XMLObjectifier.build(Nokogiri::XML(xml).root)
36
+ object.tiger.name.should == "Shere Khan"
37
+ object.lion.name.should == "Mufasa"
38
+ object.lion.dimensions.height.unit.should == "inches"
39
+ object.lion.dimensions.height.value.should == "30"
40
+ object.lion.dimensions.length.unit.should == "meters"
41
+ object.lion.dimensions.length.value.should == "2"
42
+ end
43
+
44
+ it "should underscore node names" do
45
+ xml = "<Person><PersonName>Ronald</PersonName></Person>"
46
+ object = FacturaePrint::XMLObjectifier.build(Nokogiri::XML(xml))
47
+ object.person.person_name.should == "Ronald"
48
+ end
49
+
50
+ it "should ignore empty nodes" do
51
+ xml = "<Person><PersonName>Ronald</PersonName><PersonSurname/></Person>"
52
+ object = FacturaePrint::XMLObjectifier.build(Nokogiri::XML(xml))
53
+ object.person.person_name.should == "Ronald"
54
+ object.person.person_surname.should be_nil
55
+ end
56
+
57
+ it "should build on a given object" do
58
+ xml = "<arm><wrist>2</wrist><hand>3</hand></arm>"
59
+ object = OpenStruct.new({"elbow" => "1"})
60
+
61
+ FacturaePrint::XMLObjectifier.build(Nokogiri::XML(xml).root, :object => object)
62
+ object.elbow.should == "1"
63
+ object.wrist.should == "2"
64
+ object.hand.should == "3"
65
+ end
66
+
67
+ it "should build array for nodes with name in collection_nodes option" do
68
+ xml = <<-XML
69
+ <agenda>
70
+ <size>3</size>
71
+ <contacts>
72
+ <contact>
73
+ <name>Muddy Waters</name>
74
+ <numbers>
75
+ <number>555-1234</number>
76
+ <number>555-2341</number>
77
+ </numbers>
78
+ </contact>
79
+ <contact>
80
+ <name>Keith Richards</name>
81
+ <numbers>
82
+ <number>555-5678</number>
83
+ </numbers>
84
+ </contact>
85
+ <contact>
86
+ <name>Steve Marriott</name>
87
+ </contact>
88
+ </contacts>
89
+ </agenda>
90
+ XML
91
+
92
+ object = FacturaePrint::XMLObjectifier.build(Nokogiri::XML(xml).root, :collection_nodes => ["contacts", "numbers"])
93
+ object.size.should == "3"
94
+ object.contacts.should be_a(Array)
95
+ object.contacts[0].name.should == "Muddy Waters"
96
+ object.contacts[0].numbers.should be_a(Array)
97
+ object.contacts[0].numbers.size.should == 2
98
+ object.contacts[0].numbers[0].should == "555-1234"
99
+ object.contacts[0].numbers[1].should == "555-2341"
100
+ object.contacts[1].name.should == "Keith Richards"
101
+ object.contacts[1].numbers.should be_a(Array)
102
+ object.contacts[1].numbers.size.should == 1
103
+ object.contacts[1].numbers[0].should == "555-5678"
104
+ object.contacts[2].name.should == "Steve Marriott"
105
+ object.contacts[2].numbers.should be_nil
106
+ end
107
+
108
+ end
@@ -0,0 +1,127 @@
1
+ body {
2
+ font-family: Helvetica, Arial, sans-serif;
3
+ font-size: 14px;
4
+ }
5
+
6
+ .wrapper {
7
+ position: relative;
8
+ overflow: hidden;
9
+ }
10
+
11
+ h1 {
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ dl {
17
+ margin: 0;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .header {
22
+ position: absolute;
23
+ top: 0;
24
+ right: 0;
25
+ }
26
+
27
+ .header h1 {
28
+ margin-top: -5px;
29
+ font-size: 35px;
30
+ color: #666;
31
+ }
32
+
33
+ .seller-data .name,
34
+ .buyer-data .name {
35
+ font-size: 1.2em;
36
+ padding-bottom: 5px;
37
+ }
38
+
39
+ .seller-data p,
40
+ .buyer-data p {
41
+ margin: 0;
42
+ }
43
+
44
+ .invoice-header-data dl dt,
45
+ .invoice-header-data dl dd,
46
+ .buyer-data dt,
47
+ .buyer-data dd {
48
+ float: left;
49
+ margin: 0;
50
+ padding-bottom: 5px;
51
+ }
52
+
53
+ .invoice-header-data dl dt,
54
+ .buyer-data dl dt {
55
+ clear: left;
56
+ width: 150px;
57
+ font-weight: bold;
58
+ }
59
+
60
+ .invoice-header-data {
61
+ overflow: hidden;
62
+ margin: 30px 0 25px 0;
63
+ }
64
+
65
+ .buyer-data {
66
+ padding-bottom: 40px;
67
+ }
68
+
69
+ .invoice-details {
70
+ width: 100%;
71
+ border-collapse:collapse;
72
+ border-bottom: 1px solid #000;
73
+ }
74
+
75
+ .invoice-details .col1 {
76
+ width: 80%;
77
+ }
78
+
79
+ .invoice-details .col2 {
80
+ width: 20%
81
+ }
82
+
83
+ .invoice-details th {
84
+ padding: 5px 0;
85
+ border: 1px solid #000;
86
+ }
87
+
88
+ .invoice-details td {
89
+ padding: 10px 20px;
90
+ }
91
+
92
+ .invoice-details .description {
93
+ border-left: 1px solid #000;
94
+ border-right: 1px solid #000;
95
+ }
96
+
97
+ .invoice-details .amount {
98
+ text-align: right;
99
+ border-right: 1px solid #000;
100
+ }
101
+
102
+ .invoice-details .invoice-subtotal td {
103
+ border-top: 1px solid #000;
104
+ }
105
+
106
+ .invoice-details .invoice-taxes td {
107
+ padding-top: 0px;
108
+ }
109
+
110
+ .invoice-details .invoice-subtotal .description,
111
+ .invoice-details .invoice-taxes .description,
112
+ .invoice-details .invoice-total .description {
113
+ text-align: right;
114
+ }
115
+
116
+ .invoice-details .invoice-taxes .description,
117
+ .invoice-details .invoice-total .description {
118
+ font-weight: bold;
119
+ }
120
+
121
+ .invoice-details .invoice-total td {
122
+ border-top: 1px solid #000;
123
+ }
124
+
125
+ .footer {
126
+ margin-top: 40px;
127
+ }
@@ -0,0 +1,86 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="stylesheet" type="text/css" href="./default.css" />
6
+ </head>
7
+ <body>
8
+ <div class="wrapper">
9
+ <div class="header">
10
+ <h1>Factura</h1>
11
+ </div>
12
+ <div class="seller-data">
13
+ <p class="name"><%= individual_full_name(@invoice.parties.seller_party.individual) %></p>
14
+ <p><%= @invoice.parties.seller_party.individual.address_in_spain.address %></p>
15
+ <p><%= @invoice.parties.seller_party.individual.address_in_spain.post_code %> <%= @invoice.parties.seller_party.individual.address_in_spain.town %></p>
16
+ <p><%= @invoice.parties.seller_party.tax_identification.tax_identification_number %></p>
17
+ </div>
18
+ <div class="invoice-header-data">
19
+ <dl>
20
+ <dt>Factura num.</dt>
21
+ <dd><%= @invoice.invoices.first.invoice_header.invoice_number %></dd>
22
+ <dt>Fecha factura</dt>
23
+ <dd><%= @invoice.invoices.first.invoice_issue_data.issue_date %></dd>
24
+ </dl>
25
+ </div>
26
+ <div class="buyer-data">
27
+ <dl>
28
+ <dt>Facturar a</dt>
29
+ <dd>
30
+ <p class="name"><%= @invoice.parties.buyer_party.legal_entity.corporate_name %></p>
31
+ <p><%= @invoice.parties.buyer_party.legal_entity.address_in_spain.address %></p>
32
+ <p><%= @invoice.parties.buyer_party.legal_entity.address_in_spain.post_code %> <%= @invoice.parties.buyer_party.legal_entity.address_in_spain.town %></p>
33
+ <p><%= @invoice.parties.buyer_party.tax_identification.tax_identification_number %></p>
34
+ </dd>
35
+ </div>
36
+ <table class="invoice-details">
37
+ <colgroup>
38
+ <col class="col1"/>
39
+ <col class="col2"/>
40
+ </colgroup>
41
+ <thead>
42
+ <tr>
43
+ <th>Concepto</th>
44
+ <th>Importe</th>
45
+ </tr>
46
+ </thead>
47
+ <tbody class="invoice-items">
48
+ <% for item in @invoice.invoices.first.items %>
49
+ <tr>
50
+ <td class="description"><%= item.item_description %></td>
51
+ <td class="amount"><%= to_currency(item.total_cost) %></td>
52
+ </tr>
53
+ <% end %>
54
+ <% (8 - @invoice.invoices.first.items.size).times do %>
55
+ <tr><td class="description"></td><td class="amount"></td></tr>
56
+ <% end %>
57
+ </tbody>
58
+ <tbody class="invoice-subtotal">
59
+ <tr>
60
+ <td class="description">Base imponible</td>
61
+ <td class="amount"><%= to_currency(@invoice.invoices.first.invoice_totals.total_gross_amount_before_taxes) %></td>
62
+ </tr>
63
+ </tbody>
64
+ <tbody class="invoice-taxes">
65
+ <tr>
66
+ <td class="description">IRPF 15%</td>
67
+ <td class="amount">-97.50 €</td>
68
+ </tr>
69
+ </tbody>
70
+ <tbody class="invoice-total">
71
+ <tr>
72
+ <td class="description">TOTAL</td>
73
+ <td class="amount"><%= to_currency(@invoice.invoices.first.invoice_totals.invoice_total) %></td>
74
+ </tr>
75
+ </tbody>
76
+ </table>
77
+ <div class="footer">
78
+ <p>Pago: 30 días de factura</p>
79
+ <p>
80
+ Transferencia bancaria:<br/>
81
+ 1234 5678 90 1234567890
82
+ </p>
83
+ </div>
84
+ </div>
85
+ </body>
86
+ </html>
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: facturae_print
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Alvaro Bautista
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-23 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: nokogiri
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 15
30
+ segments:
31
+ - 1
32
+ - 4
33
+ - 4
34
+ version: 1.4.4
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: erubis
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 27
46
+ segments:
47
+ - 2
48
+ - 6
49
+ - 6
50
+ version: 2.6.6
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: wkhtmltopdf-binary
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 17
62
+ segments:
63
+ - 0
64
+ - 9
65
+ - 5
66
+ - 1
67
+ version: 0.9.5.1
68
+ type: :runtime
69
+ version_requirements: *id003
70
+ - !ruby/object:Gem::Dependency
71
+ name: pdfkit
72
+ prerelease: false
73
+ requirement: &id004 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ hash: 11
79
+ segments:
80
+ - 0
81
+ - 5
82
+ - 0
83
+ version: 0.5.0
84
+ type: :runtime
85
+ version_requirements: *id004
86
+ - !ruby/object:Gem::Dependency
87
+ name: thor
88
+ prerelease: false
89
+ requirement: &id005 !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 43
95
+ segments:
96
+ - 0
97
+ - 14
98
+ - 6
99
+ version: 0.14.6
100
+ type: :runtime
101
+ version_requirements: *id005
102
+ - !ruby/object:Gem::Dependency
103
+ name: rspec
104
+ prerelease: false
105
+ requirement: &id006 !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ hash: 31
111
+ segments:
112
+ - 2
113
+ - 4
114
+ - 0
115
+ version: 2.4.0
116
+ type: :development
117
+ version_requirements: *id006
118
+ description: facturae_print translates a facturae xml file into an understandable HTML or PDF file
119
+ email:
120
+ - alvarobp@gmail.com
121
+ executables:
122
+ - facturae_print
123
+ extensions: []
124
+
125
+ extra_rdoc_files: []
126
+
127
+ files:
128
+ - .gitignore
129
+ - Gemfile
130
+ - LICENSE
131
+ - README.md
132
+ - Rakefile
133
+ - bin/facturae_print
134
+ - facturae_print.gemspec
135
+ - lib/facturae_print.rb
136
+ - lib/facturae_print/cli.rb
137
+ - lib/facturae_print/core_ext/string.rb
138
+ - lib/facturae_print/invoice.rb
139
+ - lib/facturae_print/renderers/html_renderer.rb
140
+ - lib/facturae_print/renderers/html_renderer_helpers.rb
141
+ - lib/facturae_print/renderers/pdf_renderer.rb
142
+ - lib/facturae_print/version.rb
143
+ - lib/facturae_print/xml_objectifier.rb
144
+ - spec/fixtures/facturae.xml
145
+ - spec/invoice_spec.rb
146
+ - spec/renderers/html_renderer_spec.rb
147
+ - spec/renderers/pdf_renderer_spec.rb
148
+ - spec/spec_helper.rb
149
+ - spec/xml_objectifier_spec.rb
150
+ - templates/default.css
151
+ - templates/default.eruby
152
+ has_rdoc: true
153
+ homepage: ""
154
+ licenses: []
155
+
156
+ post_install_message:
157
+ rdoc_options: []
158
+
159
+ require_paths:
160
+ - lib
161
+ required_ruby_version: !ruby/object:Gem::Requirement
162
+ none: false
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ hash: 3
167
+ segments:
168
+ - 0
169
+ version: "0"
170
+ required_rubygems_version: !ruby/object:Gem::Requirement
171
+ none: false
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ hash: 3
176
+ segments:
177
+ - 0
178
+ version: "0"
179
+ requirements: []
180
+
181
+ rubyforge_project:
182
+ rubygems_version: 1.4.2
183
+ signing_key:
184
+ specification_version: 3
185
+ summary: facturae_print translates a facturae xml file into an understandable HTML or PDF file
186
+ test_files:
187
+ - spec/fixtures/facturae.xml
188
+ - spec/invoice_spec.rb
189
+ - spec/renderers/html_renderer_spec.rb
190
+ - spec/renderers/pdf_renderer_spec.rb
191
+ - spec/spec_helper.rb
192
+ - spec/xml_objectifier_spec.rb