facturae_print 0.0.2

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.
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