payday 1.0.0beta1

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,7 @@
1
+ tmp/
2
+ doc/
3
+ .yardoc/
4
+ *.gem
5
+ Gemfile.lock
6
+ .bundle
7
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "rake"
7
+ gem "yard"
8
+ end
9
+
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ Payday!
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.
4
+
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 config/initializers/mime_types.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
+ Be sure to restart your server after you edit the mime_types initializer. The updating setting won't take effect until you do.
51
+
52
+ Contributing
53
+ ===
54
+ 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!
55
+
56
+ To Do
57
+ ===
58
+ Here's what we're planning on working on with Payday in the near future:
59
+
60
+ * Package as gem
61
+ * Document how to use with ActiveRecord
62
+ * Release 1.0!
63
+
64
+ * Add support for currencies other than USD
65
+ * Add support for Money gem or BigDecimal or general numerics (right now we support BigDecimal and general numerics)
66
+ * Add support for blank line items
67
+ * Add support for indented line items
68
+ * Apply different tax rates to different line items
69
+ * Add support for shipping either pre or post tax
70
+ * Add ability to set pdf document size to something other than 8.5 x 11
71
+ * Add invoice_details has for stuff under the invoice number
72
+ * Add ability to show skus or product ids on each line item
73
+
74
+ * Add page numbers
75
+ * Ability to render invoice to html for web viewing
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+
6
+ task :default => :test
7
+
8
+ Bundler::GemHelper.install_tasks
9
+
10
+ Rake::TestTask.new do |t|
11
+ t.test_files = FileList['test/**/*_test.rb']
12
+ end
Binary file
data/lib/payday.rb ADDED
@@ -0,0 +1,12 @@
1
+ # Not much to see here
2
+ require 'date'
3
+ require 'bigdecimal'
4
+ require 'prawn'
5
+
6
+ require File.join(File.dirname(__FILE__), "payday", "version")
7
+ require File.join(File.dirname(__FILE__), "payday", "config")
8
+ require File.join(File.dirname(__FILE__), "payday", "line_itemable")
9
+ require File.join(File.dirname(__FILE__), "payday", "line_item")
10
+ require File.join(File.dirname(__FILE__), "payday", "pdf_renderer")
11
+ require File.join(File.dirname(__FILE__), "payday", "invoiceable")
12
+ require File.join(File.dirname(__FILE__), "payday", "invoice")
@@ -0,0 +1,21 @@
1
+ module Payday
2
+
3
+ # Configuration for Payday. This is a singleton, so to set the company_name you would call
4
+ # +Payday::Config.default.company_name = "Awesome Corp"+.
5
+ class Config
6
+ attr_accessor :invoice_logo, :company_name, :company_details, :date_format, :currency
7
+
8
+ # Returns the default configuration instance
9
+ def self.default
10
+ @@default ||= new
11
+ end
12
+
13
+ private
14
+ def initialize
15
+ self.invoice_logo = File.join(File.dirname(__FILE__), "..", "..", "assets", "default_logo.png")
16
+ self.company_name = "Awesome Corp"
17
+ self.company_details = "awesomecorp@commondream.net"
18
+ self.date_format = "%B %e, %Y"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module Payday
2
+
3
+ # Basically just an invoice. Stick a ton of line items in it, add some details, and then render it out!
4
+ class Invoice
5
+ include Payday::Invoiceable
6
+
7
+ attr_accessor :invoice_number, :bill_to, :ship_to, :notes, :line_items, :tax_rate, :tax_description, :due_on, :paid_at
8
+
9
+ def initialize(options = {})
10
+ self.invoice_number = options[:invoice_number] || nil
11
+ self.bill_to = options[:bill_to] || nil
12
+ self.ship_to = options[:ship_to] || nil
13
+ self.notes = options[:notes] || nil
14
+ self.line_items = options[:line_items] || []
15
+ self.tax_rate = options[:tax_rate] || nil
16
+ self.tax_description = options[:tax_description] || nil
17
+ self.due_on = options[:due_on] || nil
18
+ self.paid_at = options[:paid_at] || nil
19
+ end
20
+
21
+ # The tax rate that we're applying, as a BigDecimal
22
+ def tax_rate=(value)
23
+ @tax_rate = BigDecimal.new(value.to_s)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,60 @@
1
+ # Include {Payday::Invoiceable} in your Invoice class to make it Payday compatible. Payday
2
+ # expects that a +line_items+ method containing an Enumerable of {Payday::LineItem} compatible
3
+ # elements exists. Those LineItem objects primarily need to include quantity, price, and description methods.
4
+ #
5
+ # The +bill_to+ method should always be overwritten by your class. Otherwise, it'll say that your invoice should
6
+ # be billed to Goofy McGoofison. +ship_to+ is also available, but will not be used in rendered invoices if it
7
+ # doesn't exist.
8
+ #
9
+ # Although not required, if a +tax_rate+ method exists, {Payday::Invoiceable} will use it to calculate tax
10
+ # when generating an invoice. We include a simple tax method that calculates tax, but it's probably wiser
11
+ # to override this in your class (our calculated tax won't be stored to a database by default, for example).
12
+ #
13
+ # If the +due_on+ and +paid_at+ methods are available, {Payday::Invoiceable} will use them to show due dates and
14
+ # paid dates, as well as stamps showing if the invoice is paid or due.
15
+ module Payday::Invoiceable
16
+
17
+ # Who the invoice is being sent to.
18
+ def bill_to
19
+ "Goofy McGoofison\nYour Invoice Doesn't\nHave It's Own BillTo Method"
20
+ end
21
+
22
+ # Calculates the subtotal of this invoice by adding up all of the line items
23
+ def subtotal
24
+ line_items.inject(BigDecimal.new("0")) { |result, item| result += item.amount }
25
+ end
26
+
27
+ # The tax for this invoice, as a BigDecimal
28
+ def tax
29
+ if defined?(tax_rate)
30
+ calculated = subtotal * tax_rate
31
+ return 0 if calculated < 0
32
+ calculated
33
+ else
34
+ 0
35
+ end
36
+ end
37
+
38
+ # Calculates the total for this invoice.
39
+ def total
40
+ subtotal + tax
41
+ end
42
+
43
+ def overdue?
44
+ defined?(:due_on) && due_on.is_a?(Date) && due_on < Date.today && !paid_at
45
+ end
46
+
47
+ def paid?
48
+ defined?(:paid_at) && !!paid_at
49
+ end
50
+
51
+ # Renders this invoice to pdf as a string
52
+ def render_pdf
53
+ Payday::PdfRenderer.render(self)
54
+ end
55
+
56
+ # Renders this invoice to pdf
57
+ def render_pdf_to_file(path)
58
+ Payday::PdfRenderer.render_to_file(self, path)
59
+ end
60
+ end
@@ -0,0 +1,29 @@
1
+ module Payday
2
+ # Represents a line item in an invoice.
3
+ #
4
+ # +quantity+ and +price+ are written to be pretty picky, primarily because if we're not picky about what values are set to
5
+ # them your invoice math could get pretty messed up. It's recommended that both values be set to +BigDecimal+ values.
6
+ # Otherwise, we'll do our best to convert the set values to a +BigDecimal+.
7
+ class LineItem
8
+ include LineItemable
9
+
10
+ attr_accessor :description, :quantity, :price
11
+
12
+ # Initializes a new LineItem
13
+ def initialize(options = {})
14
+ self.quantity = options[:quantity] || "1"
15
+ self.price = options[:price] || "0.00"
16
+ self.description = options[:description] || ""
17
+ end
18
+
19
+ # Sets the quantity of this {LineItem}
20
+ def quantity=(value)
21
+ @quantity = BigDecimal.new(value.to_s)
22
+ end
23
+
24
+ # Sets the price for this {LineItem}
25
+ def price=(value)
26
+ @price = BigDecimal.new(value.to_s)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ # Include this module into your line item implementation to make sure that Payday stays happy
2
+ # with it, or just make sure that your line item implements the amount method.
3
+ module Payday::LineItemable
4
+ # Returns the total amount for this {LineItemable}, or {#price} * {#quantity}
5
+ def amount
6
+ price * quantity
7
+ end
8
+ end
@@ -0,0 +1,220 @@
1
+ module Payday
2
+
3
+ # The PDF renderer. We use this internally in Payday to render pdfs, but really you should just need to call
4
+ # {{Payday::Invoiceable#render_pdf}} to render pdfs yourself.
5
+ class PdfRenderer
6
+
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
13
+ def self.render(invoice)
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
+ stamp(invoice, pdf)
25
+ company_banner(invoice, pdf)
26
+ bill_to_ship_to(invoice, pdf)
27
+ invoice_details(invoice, pdf)
28
+ line_items_table(invoice, pdf)
29
+ totals_lines(invoice, pdf)
30
+ notes(invoice, pdf)
31
+
32
+ pdf
33
+ end
34
+
35
+ def self.stamp(invoice, pdf)
36
+ stamp = nil
37
+ if invoice.paid?
38
+ stamp = "PAID"
39
+ elsif invoice.overdue?
40
+ stamp = "OVERDUE"
41
+ end
42
+
43
+ if stamp
44
+ pdf.bounding_box([200, pdf.cursor - 50], :width => pdf.bounds.width - 400) do
45
+ pdf.font("Helvetica-Bold") do
46
+ pdf.fill_color "cc0000"
47
+ pdf.text stamp, :align=> :center, :size => 25, :rotate => 15
48
+ end
49
+ end
50
+ end
51
+
52
+ pdf.fill_color "000000"
53
+ end
54
+
55
+ def self.company_banner(invoice, pdf)
56
+ # render the logo
57
+ logo_info = pdf.image(invoice_or_default(invoice, :invoice_logo), :at => pdf.bounds.top_left, :fit => [200, 100])
58
+
59
+ # render the company details
60
+ table_data = []
61
+ table_data << [bold_cell(pdf, invoice_or_default(invoice, :company_name), :size => 12)]
62
+ table_data << [invoice_or_default(invoice, :company_details)]
63
+ table = pdf.make_table(table_data, :cell_style => { :borders => [], :padding => [2, 0] })
64
+ pdf.bounding_box([pdf.bounds.width - table.width, pdf.bounds.top], :width => table.width, :height => table.height) do
65
+ table.draw
66
+ end
67
+
68
+ pdf.move_cursor_to(pdf.bounds.top - logo_info.scaled_height - 20)
69
+ end
70
+
71
+ def self.bill_to_ship_to(invoice, pdf)
72
+ bill_to_cell_style = { :borders => [], :padding => [2, 0]}
73
+ bill_to_ship_to_bottom = 0
74
+
75
+ # render bill to
76
+ pdf.float do
77
+ table = pdf.table([[bold_cell(pdf, "Bill To")], [invoice.bill_to]], :column_widths => [200], :cell_style => bill_to_cell_style)
78
+ bill_to_ship_to_bottom = pdf.cursor
79
+ end
80
+
81
+ # render ship to
82
+ if defined?(invoice.ship_to)
83
+ table = pdf.make_table([[bold_cell(pdf, "Ship To")], [invoice.ship_to]], :column_widths => [200],
84
+ :cell_style => bill_to_cell_style)
85
+
86
+ pdf.bounding_box([pdf.bounds.width - table.width, pdf.cursor], :width => table.width, :height => table.height + 2) do
87
+ table.draw
88
+ end
89
+ end
90
+
91
+ # make sure we start at the lower of the bill_to or ship_to details
92
+ bill_to_ship_to_bottom = pdf.cursor if pdf.cursor < bill_to_ship_to_bottom
93
+ pdf.move_cursor_to(bill_to_ship_to_bottom - 20)
94
+ end
95
+
96
+ def self.invoice_details(invoice, pdf)
97
+ # invoice details
98
+ table_data = []
99
+
100
+ # invoice number
101
+ if defined?(invoice.invoice_number) && invoice.invoice_number
102
+ table_data << [bold_cell(pdf, "Invoice #:"), bold_cell(pdf, invoice.invoice_number.to_s, :align => :right)]
103
+ end
104
+
105
+ # Due on
106
+ if defined?(invoice.due_on) && invoice.due_on
107
+ if invoice.due_on.is_a?(Date)
108
+ due_date = invoice.due_on.strftime(Payday::Config.default.date_format)
109
+ else
110
+ due_date = invoice.due_on.to_s
111
+ end
112
+
113
+ table_data << [bold_cell(pdf, "Due Date:"), bold_cell(pdf, due_date, :align => :right)]
114
+ end
115
+
116
+ # Paid on
117
+ if defined?(invoice.paid_at) && invoice.paid_at
118
+ if invoice.paid_at.is_a?(Date)
119
+ paid_date = invoice.paid_at.strftime(Payday::Config.default.date_format)
120
+ else
121
+ paid_date = invoice.paid_at.to_s
122
+ end
123
+
124
+ table_data << [bold_cell(pdf, "Paid Date:"), bold_cell(pdf, paid_date, :align => :right)]
125
+ end
126
+
127
+ if table_data.length > 0
128
+ pdf.table(table_data, :cell_style => { :borders => [], :padding => 1 })
129
+ end
130
+ end
131
+
132
+ def self.line_items_table(invoice, pdf)
133
+ table_data = []
134
+ table_data << [bold_cell(pdf, "Description", :borders => []),
135
+ bold_cell(pdf, "Unit Price", :align => :center, :borders => []),
136
+ bold_cell(pdf, "Quantity", :align => :center, :borders => []),
137
+ bold_cell(pdf, "Amount", :align => :center, :borders => [])]
138
+ invoice.line_items.each do |line|
139
+ table_data << [line.description, number_to_currency(line.price), line.quantity.to_s,
140
+ number_to_currency(line.amount)]
141
+ end
142
+
143
+ pdf.move_cursor_to(pdf.cursor - 20)
144
+ pdf.table(table_data, :width => pdf.bounds.width, :header => true, :cell_style => {:border_width => 0.5, :border_color => "cccccc", :padding => [5, 10]},
145
+ :row_colors => ["dfdfdf", "ffffff"]) do
146
+ # left align the number columns
147
+ columns(1..3).rows(1..row_length - 1).style(:align => :right)
148
+
149
+ # set the column widths correctly
150
+ natural = natural_column_widths
151
+ natural[0] = width - natural[1] - natural[2] - natural[3]
152
+
153
+ column_widths = natural
154
+ end
155
+ end
156
+
157
+ def self.totals_lines(invoice, pdf)
158
+ table_data = []
159
+ table_data << [bold_cell(pdf, "Subtotal:"), cell(pdf, number_to_currency(invoice.subtotal), :align => :right)]
160
+ table_data << [bold_cell(pdf, "Tax:"), cell(pdf, number_to_currency(invoice.tax), :align => :right)]
161
+ table_data << [bold_cell(pdf, "Total:", :size => 12),
162
+ cell(pdf, number_to_currency(invoice.total), :size => 12, :align => :right)]
163
+ table = pdf.make_table(table_data, :cell_style => { :borders => [] })
164
+ pdf.bounding_box([pdf.bounds.width - table.width, pdf.cursor], :width => table.width, :height => table.height + 2) do
165
+ table.draw
166
+ end
167
+ end
168
+
169
+ def self.notes(invoice, pdf)
170
+ if defined?(invoice.notes) && invoice.notes
171
+ pdf.move_cursor_to(pdf.cursor - 30)
172
+ pdf.font("Helvetica-Bold") do
173
+ pdf.text("Notes")
174
+ end
175
+ pdf.line_width = 0.5
176
+ pdf.stroke_color = "cccccc"
177
+ pdf.stroke_line([0, pdf.cursor - 3, pdf.bounds.width, pdf.cursor - 3])
178
+ pdf.move_cursor_to(pdf.cursor - 10)
179
+ pdf.text(invoice.notes.to_s)
180
+ end
181
+ end
182
+
183
+ def self.invoice_or_default(invoice, property)
184
+ if invoice.respond_to?(property) && invoice.send(property)
185
+ invoice.send(property)
186
+ else
187
+ Payday::Config.default.send(property)
188
+ end
189
+ end
190
+
191
+ def self.cell(pdf, text, options = {})
192
+ Prawn::Table::Cell::Text.make(pdf, text, options)
193
+ end
194
+
195
+ def self.bold_cell(pdf, text, options = {})
196
+ options[:font] = "Helvetica-Bold"
197
+ cell(pdf, text, options)
198
+ end
199
+
200
+ # from Rails, I think
201
+ def self.number_with_delimiter(number, delimiter=",")
202
+ number.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
203
+ end
204
+
205
+ def self.number_to_currency(number)
206
+ number_with_delimiter(sprintf("$%.02f", number))
207
+ end
208
+
209
+ def self.max_cell_width(cell_proxy)
210
+ max = 0
211
+ cell_proxy.each do |cell|
212
+ if cell.natural_content_width > max
213
+ max = cell.natural_content_width
214
+ end
215
+ end
216
+
217
+ max
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,3 @@
1
+ module Payday
2
+ VERSION = "1.0.0beta1"
3
+ end
data/payday.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "payday/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "payday"
7
+ s.version = Payday::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Alan Johnson"]
10
+ s.email = ["alan@commondream.net"]
11
+ s.homepage = ""
12
+ s.summary = %q{git remote add origin git@github.com:commondream/payday.git}
13
+ s.description = %q{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.}
14
+
15
+ s.add_dependency("prawn", "~> 0.11.1.pre")
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+ end
@@ -0,0 +1,116 @@
1
+ require "test/test_helper"
2
+
3
+ module Payday
4
+ class InvoiceTest < Test::Unit::TestCase
5
+ test "that setting values through the options hash on initialization works" do
6
+ i = Invoice.new(:invoice_number => 20, :bill_to => "Here", :ship_to => "There",
7
+ :notes => "These are some notes.",
8
+ :line_items => [LineItem.new(:price => 10, :quantity => 3, :description => "Shirts")],
9
+ :tax_rate => 12.5, :tax_description => "Local Sales Tax, 12.5%")
10
+
11
+ assert_equal 20, i.invoice_number
12
+ assert_equal "Here", i.bill_to
13
+ assert_equal "There", i.ship_to
14
+ assert_equal "These are some notes.", i.notes
15
+ assert_equal "Shirts", i.line_items[0].description
16
+ assert_equal BigDecimal.new("12.5"), i.tax_rate
17
+ assert_equal "Local Sales Tax, 12.5%", i.tax_description
18
+ end
19
+
20
+ test "that subtotal totals up all of the line items in an invoice correctly" do
21
+ i = Invoice.new
22
+
23
+ # $100 in Pants
24
+ i.line_items << LineItem.new(:price => 20, :quantity => 5, :description => "Pants")
25
+
26
+ # $30 in Shirts
27
+ i.line_items << LineItem.new(:price => 10, :quantity => 3, :description => "Shirts")
28
+
29
+ # $1000 in Hats
30
+ i.line_items << LineItem.new(:price => 5, :quantity => 200, :description => "Hats")
31
+
32
+ assert_equal BigDecimal.new("1130"), i.subtotal
33
+ end
34
+
35
+ test "that tax returns the correct tax amount, rounded to two decimal places" do
36
+ i = Invoice.new(:tax_rate => 0.1)
37
+ i.line_items << LineItem.new(:price => 20, :quantity => 5, :description => "Pants")
38
+
39
+ assert_equal(BigDecimal.new("10"), i.tax)
40
+ end
41
+
42
+ test "that taxes aren't applied to invoices with a subtotal of 0 or a negative amount" do
43
+ i = Invoice.new(:tax_rate => 0.1)
44
+ i.line_items << LineItem.new(:price => -1, :quantity => 100, :description => "Negative Priced Pants")
45
+
46
+ assert_equal(BigDecimal.new("0"), i.tax)
47
+ end
48
+
49
+ test "that the total for this invoice calculates correctly" do
50
+ i = Invoice.new(:tax_rate => 0.1)
51
+
52
+ # $100 in Pants
53
+ i.line_items << LineItem.new(:price => 20, :quantity => 5, :description => "Pants")
54
+
55
+ # $30 in Shirts
56
+ i.line_items << LineItem.new(:price => 10, :quantity => 3, :description => "Shirts")
57
+
58
+ # $1000 in Hats
59
+ i.line_items << LineItem.new(:price => 5, :quantity => 200, :description => "Hats")
60
+
61
+ assert_equal BigDecimal.new("1243"), i.total
62
+ end
63
+
64
+ test "overdue? is false when past date and unpaid" do
65
+ i = Invoice.new(:due_on => Date.today - 1)
66
+ assert i.overdue?
67
+ end
68
+
69
+ test "overdue? is true when past date but paid" do
70
+ i = Invoice.new(:due_on => Date.today - 1, :paid_at => Date.today)
71
+ assert !i.overdue?
72
+ end
73
+
74
+ test "paid is false when not paid" do
75
+ i = Invoice.new
76
+ assert !i.paid?
77
+ end
78
+
79
+ test "paid is true when paid" do
80
+ i = Invoice.new(:paid_at => Date.today)
81
+ assert i.paid?
82
+ end
83
+
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
+
88
+ i = Invoice.new(:tax_rate => 0.1, :notes => "These are some crazy awesome notes!", :invoice_number => 12,
89
+ :due_on => Date.civil(2011, 1, 22), :paid_at => Date.civil(2012, 2, 22),
90
+ :bill_to => "Alan Johnson\n101 This Way\nSomewhere, SC 22222", :ship_to => "Frank Johnson\n101 That Way\nOther, SC 22229")
91
+
92
+ 3.times do
93
+ i.line_items << LineItem.new(:price => 20, :quantity => 5, :description => "Pants")
94
+ i.line_items << LineItem.new(:price => 10, :quantity => 3, :description => "Shirts")
95
+ i.line_items << LineItem.new(:price => 5, :quantity => 200, :description => "Hats")
96
+ end
97
+
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_at => 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
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,25 @@
1
+ require "test/test_helper"
2
+
3
+ module Payday
4
+ class LineItemTest < Test::Unit::TestCase
5
+ test "initializing with a price" do
6
+ li = LineItem.new(:price => BigDecimal.new("20"))
7
+ assert_equal BigDecimal.new("20"), li.price
8
+ end
9
+
10
+ test "initializing with a quantity" do
11
+ li = LineItem.new(:quantity => 30)
12
+ assert_equal BigDecimal.new("30"), li.quantity
13
+ end
14
+
15
+ test "initializing with a description" do
16
+ li = LineItem.new(:description => "12 Pairs of Pants")
17
+ assert_equal "12 Pairs of Pants", li.description
18
+ end
19
+
20
+ test "that amount returns the correct amount" do
21
+ li = LineItem.new(:price => 10, :quantity => 12)
22
+ assert_equal BigDecimal.new("120"), li.amount
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ require File.join(File.dirname(__FILE__), "..", "lib", "payday")
2
+
3
+ require 'test/unit'
4
+
5
+ # Shamelessly ripped from jm's context library: https://github.com/jm/context/blob/master/lib/context/test.rb
6
+ class Test::Unit::TestCase
7
+ class << self
8
+ # Create a test method. +name+ is a native-language string to describe the test
9
+ # (e.g., no more +test_this_crazy_thing_with_underscores+).
10
+ #
11
+ # test "A user should not be able to delete another user" do
12
+ # assert_false @user.can?(:delete, @other_user)
13
+ # end
14
+ #
15
+ def test(name, opts={}, &block)
16
+ test_name = ["test:", name].reject { |n| n == "" }.join(' ')
17
+ # puts "running test #{test_name}"
18
+ defined = instance_method(test_name) rescue false
19
+ raise "#{test_name} is already defined in #{self}" if defined
20
+
21
+ if block_given?
22
+ define_method(test_name, &block)
23
+ else
24
+ define_method(test_name) do
25
+ flunk "No implementation provided for #{name}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: payday
3
+ version: !ruby/object:Gem::Version
4
+ hash: 62196353
5
+ prerelease: 5
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ - beta
11
+ - 1
12
+ version: 1.0.0beta1
13
+ platform: ruby
14
+ authors:
15
+ - Alan Johnson
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2011-03-07 00:00:00 -05:00
21
+ default_executable:
22
+ dependencies:
23
+ - !ruby/object:Gem::Dependency
24
+ name: prawn
25
+ prerelease: false
26
+ requirement: &id001 !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ~>
30
+ - !ruby/object:Gem::Version
31
+ hash: 961915928
32
+ segments:
33
+ - 0
34
+ - 11
35
+ - 1
36
+ - pre
37
+ version: 0.11.1.pre
38
+ type: :runtime
39
+ version_requirements: *id001
40
+ description: 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.
41
+ email:
42
+ - alan@commondream.net
43
+ executables: []
44
+
45
+ extensions: []
46
+
47
+ extra_rdoc_files: []
48
+
49
+ files:
50
+ - .gitignore
51
+ - Gemfile
52
+ - README.md
53
+ - Rakefile
54
+ - assets/default_logo.png
55
+ - lib/payday.rb
56
+ - lib/payday/config.rb
57
+ - lib/payday/invoice.rb
58
+ - lib/payday/invoiceable.rb
59
+ - lib/payday/line_item.rb
60
+ - lib/payday/line_itemable.rb
61
+ - lib/payday/pdf_renderer.rb
62
+ - lib/payday/version.rb
63
+ - payday.gemspec
64
+ - test/invoice_test.rb
65
+ - test/line_item_test.rb
66
+ - test/test_helper.rb
67
+ has_rdoc: true
68
+ homepage: ""
69
+ licenses: []
70
+
71
+ post_install_message:
72
+ rdoc_options: []
73
+
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ hash: 3
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">"
89
+ - !ruby/object:Gem::Version
90
+ hash: 25
91
+ segments:
92
+ - 1
93
+ - 3
94
+ - 1
95
+ version: 1.3.1
96
+ requirements: []
97
+
98
+ rubyforge_project:
99
+ rubygems_version: 1.4.2
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: git remote add origin git@github.com:commondream/payday.git
103
+ test_files:
104
+ - test/invoice_test.rb
105
+ - test/line_item_test.rb
106
+ - test/test_helper.rb