Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Got rid of Gemfile.lock.

  • Loading branch information...
commit f65b2aac824fff866113bff9c097908d66ee05b6 1 parent a83816a
Alan Johnson authored
6 .gitignore
... ... @@ -1,3 +1,7 @@
1 1 tmp/
2 2 doc/
3   -.yardoc/
  3 +.yardoc/
  4 +*.gem
  5 +Gemfile.lock
  6 +.bundle
  7 +pkg/*
2  Gemfile
... ... @@ -1,5 +1,7 @@
1 1 source "http://rubygems.org"
2 2
  3 +gemspec
  4 +
3 5 gem "prawn", "0.11.1.pre"
4 6
5 7 group :development do
18 Gemfile.lock
... ... @@ -1,18 +0,0 @@
1   -GEM
2   - remote: http://rubygems.org/
3   - specs:
4   - Ascii85 (1.0.0)
5   - pdf-reader (0.9.1)
6   - Ascii85 (>= 0.9)
7   - prawn (0.11.1.pre)
8   - pdf-reader (>= 0.8.1)
9   - rake (0.8.7)
10   - yard (0.6.4)
11   -
12   -PLATFORMS
13   - ruby
14   -
15   -DEPENDENCIES
16   - prawn (= 0.11.1.pre)
17   - rake
18   - yard
71 README.md
Source Rendered
... ... @@ -1,4 +1,73 @@
1 1 Payday!
2 2 ===
  3 +Payday is a library for rendering invoices. At present it supports rendering invoices to pdfs, but we're planning on adding support for other formats in the near future.
3 4
4   -Payday is a library for rendering invoices. At present it supports rendering invoices to html and pdfs, but maybe we'll support other formats in the future it's needed.
  5 +Using Payday
  6 +===
  7 +It's pretty easy to use Payday with the built in objects. We include the Invoice and LineItem classes, and with them you can get started pretty quickly.
  8 +
  9 +Example:
  10 +
  11 + invoice = Payday::Invoice.new(:invoice_number => 12)
  12 + i.line_items << LineItem.new(:price => 20, :quantity => 5, :description => "Pants")
  13 + i.line_items << LineItem.new(:price => 10, :quantity => 3, :description => "Shirts")
  14 + i.line_items << LineItem.new(:price => 5, :quantity => 200, :description => "Hats")
  15 + i.render_pdf_to_file("/path/to_file.pdf")
  16 +
  17 +Customizing Your Logo and Company Name
  18 +===
  19 +Check out Payday::Config to customize your company's name, details, and logo.
  20 +
  21 +Example:
  22 +
  23 + Payday::Config.default.invoice_log = "/path/to/company/logo.png"
  24 + Payday::Config.default.company_name = "Awesome Corp"
  25 + Payday::Config.default.company_details = "10 This Way\nManhattan, NY 10001\n800-111-2222\nawesome@awesomecorp.com"
  26 +
  27 +Using Payday with ActiveRecord Objects (or any other objects, for that matter)
  28 +===
  29 +
  30 +TODO
  31 +
  32 +Rendering Payday PDFs To The Web
  33 +===
  34 +Payday's Invoiceable module includes methods for rendering pdfs to disk and for rendering them to a string. In a Rails controller, you can use the
  35 +render to string method to render a pdf directly to the browser like this:
  36 +
  37 +In environment.rb:
  38 +
  39 + Mime::Type.register 'application/pdf', :pdf
  40 +
  41 +In your controller:
  42 +
  43 + respond_to do |format|
  44 + format.html
  45 + format.pdf do
  46 + send_data invoice.render_pdf, :filename => "Invoice #12.pdf", :type => "application/pdf", :disposition => :inline
  47 + end
  48 + end
  49 +
  50 +Contributing
  51 +===
  52 +Payday is pretty young, so there's still a good bit of work to be done. Feel free to fork the project, make some changes, and send a pull request. If you're unsure about what to work on, send me a message on GitHub. I'd love the help!
  53 +
  54 +To Do
  55 +===
  56 +Here's what we're planning on working on with Payday in the near future:
  57 +
  58 +* Package as gem
  59 +* Document how to use with ActiveRecord
  60 +* Release 1.0!
  61 +
  62 +* Add support for currencies other than USD
  63 +* Add support for Money gem or BigDecimal or general numerics (right now we support BigDecimal and general numerics)
  64 +* Add support for blank line items
  65 +* Add support for indented line items
  66 +* Apply different tax rates to different line items
  67 +* Add support for shipping either pre or post tax
  68 +* Add ability to set pdf document size to something other than 8.5 x 11
  69 +* Add invoice_details has for stuff under the invoice number
  70 +* Add ability to show skus or product ids on each line item
  71 +
  72 +* Add page numbers
  73 +* Ability to render invoice to html for web viewing
4 Rakefile
... ... @@ -1,10 +1,12 @@
1   -require "bundler/setup"
  1 +require 'bundler'
2 2
3 3 require 'rake'
4 4 require 'rake/testtask'
5 5
6 6 task :default => :test
7 7
  8 +Bundler::GemHelper.install_tasks
  9 +
8 10 Rake::TestTask.new do |t|
9 11 t.test_files = FileList['test/**/*_test.rb']
10 12 end
9 lib/payday/invoiceable.rb
@@ -39,8 +39,13 @@ def paid?
39 39 defined?(:paid_on) && !!paid_on
40 40 end
41 41
42   - # Renders this invoice to pdf
43   - def render_to_pdf
  42 + # Renders this invoice to pdf as a string
  43 + def render_pdf
44 44 Payday::PdfRenderer.render(self)
45 45 end
  46 +
  47 + # Renders this invoice to pdf
  48 + def render_pdf_to_file(path)
  49 + Payday::PdfRenderer.render_to_file(self, path)
  50 + end
46 51 end
268 lib/payday/pdf_renderer.rb
@@ -4,145 +4,153 @@ module Payday
4 4 # {{Payday::Invoiceable#render_pdf}} to render pdfs yourself.
5 5 class PdfRenderer
6 6
7   - # Renders the given invoice as a pdf.
  7 + # Renders the given invoice as a pdf on disk
  8 + def self.render_to_file(invoice, path)
  9 + pdf(invoice).render_file(path)
  10 + end
  11 +
  12 + # Renders the given invoice as a pdf, returning a string
8 13 def self.render(invoice)
9   - pdf = Prawn::Document.new
10   -
11   - # set up some default styling
12   - pdf.font_size(8)
13   -
14   - # render the logo
15   - logo_info = pdf.image(invoice_or_default(invoice, :invoice_logo), :at => pdf.bounds.top_left, :fit => [200, 100])
16   -
17   - # render the stamp
18   - stamp = nil
19   - if invoice.paid?
20   - stamp = "PAID"
21   - elsif invoice.overdue?
22   - stamp = "OVERDUE"
23   - end
  14 + pdf(invoice).render
  15 + end
  16 +
  17 + private
  18 + def self.pdf(invoice)
  19 + pdf = Prawn::Document.new
  20 +
  21 + # set up some default styling
  22 + pdf.font_size(8)
  23 +
  24 + # render the logo
  25 + logo_info = pdf.image(invoice_or_default(invoice, :invoice_logo), :at => pdf.bounds.top_left, :fit => [200, 100])
24 26
25   - pdf.bounding_box([200, pdf.cursor - 50], :width => pdf.bounds.width - 400) do
26   - pdf.font("Helvetica-Bold") do
27   - pdf.fill_color "cc0000"
28   - pdf.text stamp, :align=> :center, :size => 25, :rotate => 15
  27 + # render the stamp
  28 + stamp = nil
  29 + if invoice.paid?
  30 + stamp = "PAID"
  31 + elsif invoice.overdue?
  32 + stamp = "OVERDUE"
29 33 end
30   - end
31 34
32   - pdf.fill_color "000000"
33   -
34   - # render the company details
35   - table_data = []
36   - table_data << [bold_cell(pdf, invoice_or_default(invoice, :company_name), :size => 12)]
37   - table_data << [invoice_or_default(invoice, :company_details)]
38   - table = pdf.make_table(table_data, :cell_style => { :borders => [], :padding => [2, 0] })
39   - pdf.bounding_box([pdf.bounds.width - table.width, pdf.bounds.top], :width => table.width, :height => table.height) do
40   - table.draw
41   - end
42   -
43   - bill_to_cell_style = { :borders => [], :padding => [2, 0]}
44   -
45   - # render bill to
46   - pdf.move_cursor_to(pdf.bounds.top - logo_info.scaled_height - 20)
47   - pdf.float do
48   - pdf.table([[bold_cell(pdf, "Bill To")], [invoice.bill_to]], :column_widths => [200], :cell_style => bill_to_cell_style)
49   - end
50   -
51   - # render ship to
52   - table = pdf.make_table([[bold_cell(pdf, "Ship To")], [invoice.ship_to]], :column_widths => [200],
53   - :cell_style => bill_to_cell_style)
54   - pdf.bounding_box([pdf.bounds.width - table.width, pdf.cursor], :width => table.width, :height => table.height + 2) do
55   - table.draw
56   - end
57   -
58   - # render the invoice details
59   - pdf.move_cursor_to(pdf.cursor - 20)
60   - table_data = []
61   -
62   - # invoice number
63   - if defined?(invoice.invoice_number) && invoice.invoice_number
64   - table_data << [bold_cell(pdf, "Invoice #:"), bold_cell(pdf, invoice.invoice_number.to_s, :align => :right)]
65   - end
66   -
67   - # Due on
68   - if defined?(invoice.due_on) && invoice.due_on
69   - if invoice.due_on.is_a?(Date)
70   - due_date = invoice.due_on.strftime(Payday::Config.default.date_format)
71   - else
72   - due_date = invoice.due_on.to_s
  35 + pdf.bounding_box([200, pdf.cursor - 50], :width => pdf.bounds.width - 400) do
  36 + pdf.font("Helvetica-Bold") do
  37 + pdf.fill_color "cc0000"
  38 + pdf.text stamp, :align=> :center, :size => 25, :rotate => 15
  39 + end
73 40 end
74   -
75   - table_data << [bold_cell(pdf, "Due Date:"), bold_cell(pdf, due_date, :align => :right)]
76   - end
77   -
78   - # Paid on
79   - if defined?(invoice.paid_on) && invoice.paid_on
80   - if invoice.paid_on.is_a?(Date)
81   - paid_date = invoice.paid_on.strftime(Payday::Config.default.date_format)
82   - else
83   - paid_date = invoice.paid_on.to_s
  41 +
  42 + pdf.fill_color "000000"
  43 +
  44 + # render the company details
  45 + table_data = []
  46 + table_data << [bold_cell(pdf, invoice_or_default(invoice, :company_name), :size => 12)]
  47 + table_data << [invoice_or_default(invoice, :company_details)]
  48 + table = pdf.make_table(table_data, :cell_style => { :borders => [], :padding => [2, 0] })
  49 + pdf.bounding_box([pdf.bounds.width - table.width, pdf.bounds.top], :width => table.width, :height => table.height) do
  50 + table.draw
84 51 end
85   -
86   - table_data << [bold_cell(pdf, "Paid Date:"), bold_cell(pdf, paid_date, :align => :right)]
87   - end
88   -
89   - if table_data.length > 0
90   - pdf.table(table_data, :cell_style => { :borders => [], :padding => 1 })
91   - end
92   -
93   - # render the line items
94   - table_data = []
95   - table_data << [bold_cell(pdf, "Description", :borders => []),
96   - bold_cell(pdf, "Unit Price", :align => :center, :borders => []),
97   - bold_cell(pdf, "Quantity", :align => :center, :borders => []),
98   - bold_cell(pdf, "Amount", :align => :center, :borders => [])]
99   - invoice.line_items.each do |line|
100   - table_data << [line.description, number_to_currency(line.price), line.quantity.to_s("F"),
101   - number_to_currency(line.amount)]
102   - end
103   -
104   - pdf.move_cursor_to(pdf.cursor - 20)
105   - pdf.table(table_data, :width => pdf.bounds.width, :header => true, :cell_style => {:border_width => 0.5, :border_color => "cccccc", :padding => [5, 10]},
106   - :row_colors => ["dfdfdf", "ffffff"]) do
107   - # left align the number columns
108   - columns(1..3).rows(1..row_length - 1).style(:align => :right)
109   -
110   - # set the column widths correctly
111   - natural = natural_column_widths
112   - natural[0] = width - natural[1] - natural[2] - natural[3]
113   -
114   - column_widths = natural
115   - end
116   -
117   - # render the totals lines
118   - table_data = []
119   - table_data << [bold_cell(pdf, "Subtotal:"), cell(pdf, number_to_currency(invoice.subtotal), :align => :right)]
120   - table_data << [bold_cell(pdf, "Tax:"), cell(pdf, number_to_currency(invoice.tax), :align => :right)]
121   - table_data << [bold_cell(pdf, "Total:", :size => 12),
122   - cell(pdf, number_to_currency(invoice.total), :size => 12, :align => :right)]
123   - table = pdf.make_table(table_data, :cell_style => { :borders => [] })
124   - pdf.bounding_box([pdf.bounds.width - table.width, pdf.cursor], :width => table.width, :height => table.height + 2) do
125   - table.draw
126   - end
127   -
128   - # render the notes
129   - if defined?(invoice.notes) && invoice.notes
130   - pdf.move_cursor_to(pdf.cursor - 30)
131   - pdf.font("Helvetica-Bold") do
132   - pdf.text("Notes")
  52 +
  53 + bill_to_cell_style = { :borders => [], :padding => [2, 0]}
  54 +
  55 + # render bill to
  56 + pdf.move_cursor_to(pdf.bounds.top - logo_info.scaled_height - 20)
  57 + pdf.float do
  58 + pdf.table([[bold_cell(pdf, "Bill To")], [invoice.bill_to]], :column_widths => [200], :cell_style => bill_to_cell_style)
  59 + end
  60 +
  61 + # render ship to
  62 + table = pdf.make_table([[bold_cell(pdf, "Ship To")], [invoice.ship_to]], :column_widths => [200],
  63 + :cell_style => bill_to_cell_style)
  64 + pdf.bounding_box([pdf.bounds.width - table.width, pdf.cursor], :width => table.width, :height => table.height + 2) do
  65 + table.draw
133 66 end
134   - pdf.line_width = 0.5
135   - pdf.stroke_color = "cccccc"
136   - pdf.stroke_line([0, pdf.cursor - 3, pdf.bounds.width, pdf.cursor - 3])
137   - pdf.move_cursor_to(pdf.cursor - 10)
138   - pdf.text(invoice.notes.to_s)
  67 +
  68 + # render the invoice details
  69 + pdf.move_cursor_to(pdf.cursor - 20)
  70 + table_data = []
  71 +
  72 + # invoice number
  73 + if defined?(invoice.invoice_number) && invoice.invoice_number
  74 + table_data << [bold_cell(pdf, "Invoice #:"), bold_cell(pdf, invoice.invoice_number.to_s, :align => :right)]
  75 + end
  76 +
  77 + # Due on
  78 + if defined?(invoice.due_on) && invoice.due_on
  79 + if invoice.due_on.is_a?(Date)
  80 + due_date = invoice.due_on.strftime(Payday::Config.default.date_format)
  81 + else
  82 + due_date = invoice.due_on.to_s
  83 + end
  84 +
  85 + table_data << [bold_cell(pdf, "Due Date:"), bold_cell(pdf, due_date, :align => :right)]
  86 + end
  87 +
  88 + # Paid on
  89 + if defined?(invoice.paid_on) && invoice.paid_on
  90 + if invoice.paid_on.is_a?(Date)
  91 + paid_date = invoice.paid_on.strftime(Payday::Config.default.date_format)
  92 + else
  93 + paid_date = invoice.paid_on.to_s
  94 + end
  95 +
  96 + table_data << [bold_cell(pdf, "Paid Date:"), bold_cell(pdf, paid_date, :align => :right)]
  97 + end
  98 +
  99 + if table_data.length > 0
  100 + pdf.table(table_data, :cell_style => { :borders => [], :padding => 1 })
  101 + end
  102 +
  103 + # render the line items
  104 + table_data = []
  105 + table_data << [bold_cell(pdf, "Description", :borders => []),
  106 + bold_cell(pdf, "Unit Price", :align => :center, :borders => []),
  107 + bold_cell(pdf, "Quantity", :align => :center, :borders => []),
  108 + bold_cell(pdf, "Amount", :align => :center, :borders => [])]
  109 + invoice.line_items.each do |line|
  110 + table_data << [line.description, number_to_currency(line.price), line.quantity.to_s("F"),
  111 + number_to_currency(line.amount)]
  112 + end
  113 +
  114 + pdf.move_cursor_to(pdf.cursor - 20)
  115 + pdf.table(table_data, :width => pdf.bounds.width, :header => true, :cell_style => {:border_width => 0.5, :border_color => "cccccc", :padding => [5, 10]},
  116 + :row_colors => ["dfdfdf", "ffffff"]) do
  117 + # left align the number columns
  118 + columns(1..3).rows(1..row_length - 1).style(:align => :right)
  119 +
  120 + # set the column widths correctly
  121 + natural = natural_column_widths
  122 + natural[0] = width - natural[1] - natural[2] - natural[3]
  123 +
  124 + column_widths = natural
  125 + end
  126 +
  127 + # render the totals lines
  128 + table_data = []
  129 + table_data << [bold_cell(pdf, "Subtotal:"), cell(pdf, number_to_currency(invoice.subtotal), :align => :right)]
  130 + table_data << [bold_cell(pdf, "Tax:"), cell(pdf, number_to_currency(invoice.tax), :align => :right)]
  131 + table_data << [bold_cell(pdf, "Total:", :size => 12),
  132 + cell(pdf, number_to_currency(invoice.total), :size => 12, :align => :right)]
  133 + table = pdf.make_table(table_data, :cell_style => { :borders => [] })
  134 + pdf.bounding_box([pdf.bounds.width - table.width, pdf.cursor], :width => table.width, :height => table.height + 2) do
  135 + table.draw
  136 + end
  137 +
  138 + # render the notes
  139 + if defined?(invoice.notes) && invoice.notes
  140 + pdf.move_cursor_to(pdf.cursor - 30)
  141 + pdf.font("Helvetica-Bold") do
  142 + pdf.text("Notes")
  143 + end
  144 + pdf.line_width = 0.5
  145 + pdf.stroke_color = "cccccc"
  146 + pdf.stroke_line([0, pdf.cursor - 3, pdf.bounds.width, pdf.cursor - 3])
  147 + pdf.move_cursor_to(pdf.cursor - 10)
  148 + pdf.text(invoice.notes.to_s)
  149 + end
  150 +
  151 + pdf
139 152 end
140 153
141   - # just dump it all out to a file for now
142   - pdf.render_file("tmp/testing.pdf")
143   - end
144   -
145   - private
146 154 def self.invoice_or_default(invoice, property)
147 155 if invoice.respond_to?(property) && invoice.send(property)
148 156 invoice.send(property)
22 test/invoice_test.rb
@@ -81,7 +81,10 @@ class InvoiceTest < Test::Unit::TestCase
81 81 assert i.paid?
82 82 end
83 83
84   - test "rendering to pdf" do
  84 + test "rendering to file" do
  85 + File.unlink("tmp/testing.pdf") if File.exists?("tmp/testing.pdf")
  86 + assert !File.exists?("tmp/testing.pdf")
  87 +
85 88 i = Invoice.new(:tax_rate => 0.1, :notes => "These are some crazy awesome notes!", :invoice_number => 12,
86 89 :due_on => Date.civil(2011, 1, 22), :paid_on => Date.civil(2012, 2, 22),
87 90 :bill_to => "Alan Johnson\n101 This Way\nSomewhere, SC 22222", :ship_to => "Frank Johnson\n101 That Way\nOther, SC 22229")
@@ -92,7 +95,22 @@ class InvoiceTest < Test::Unit::TestCase
92 95 i.line_items << LineItem.new(:price => 5, :quantity => 200, :description => "Hats")
93 96 end
94 97
95   - i.render_to_pdf
  98 + i.render_pdf_to_file("tmp/testing.pdf")
  99 + assert File.exists?("tmp/testing.pdf")
  100 + end
  101 +
  102 + test "rendering to string" do
  103 + i = Invoice.new(:tax_rate => 0.1, :notes => "These are some crazy awesome notes!", :invoice_number => 12,
  104 + :due_on => Date.civil(2011, 1, 22), :paid_on => Date.civil(2012, 2, 22),
  105 + :bill_to => "Alan Johnson\n101 This Way\nSomewhere, SC 22222", :ship_to => "Frank Johnson\n101 That Way\nOther, SC 22229")
  106 +
  107 + 3.times do
  108 + i.line_items << LineItem.new(:price => 20, :quantity => 5, :description => "Pants")
  109 + i.line_items << LineItem.new(:price => 10, :quantity => 3, :description => "Shirts")
  110 + i.line_items << LineItem.new(:price => 5, :quantity => 200, :description => "Hats")
  111 + end
  112 +
  113 + assert_not_nil i.render_pdf
96 114 end
97 115 end
98 116 end

0 comments on commit f65b2aa

Please sign in to comment.
Something went wrong with that request. Please try again.