simple_invoice 0.0.1

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.
Files changed (50) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +2 -0
  3. data/example/Gemfile +2 -0
  4. data/example/create_invoice_from_subscription.expected-output.txt +12 -0
  5. data/example/create_invoice_from_subscription.rb +15 -0
  6. data/example/create_one_off_invoice.expected-output.txt +13 -0
  7. data/example/create_one_off_invoice.rb +19 -0
  8. data/example/lib/example_subscriptions/gardening_subscription.rb +13 -0
  9. data/example/lib/example_subscriptions/lawn_mowing_subscription.rb +13 -0
  10. data/example/lib/invoice_number_allocator.rb +15 -0
  11. data/example/lib/invoice_plain_text_formatter.rb +61 -0
  12. data/example/run_daily_process_subscriptions.expected-output.txt +14 -0
  13. data/example/run_daily_process_subscriptions.rb +27 -0
  14. data/git-hooks/INSTALL.sh +11 -0
  15. data/git-hooks/pre-commit.sh +15 -0
  16. data/lib/simple_invoice.rb +14 -0
  17. data/lib/simple_invoice/billing_period.rb +40 -0
  18. data/lib/simple_invoice/billing_period_type.rb +44 -0
  19. data/lib/simple_invoice/billing_period_type/monthly.rb +12 -0
  20. data/lib/simple_invoice/billing_period_type/weekly.rb +12 -0
  21. data/lib/simple_invoice/config.rb +33 -0
  22. data/lib/simple_invoice/contact.rb +5 -0
  23. data/lib/simple_invoice/invoice.rb +22 -0
  24. data/lib/simple_invoice/invoice_data.rb +48 -0
  25. data/lib/simple_invoice/invoice_template.rb +15 -0
  26. data/lib/simple_invoice/line_item.rb +10 -0
  27. data/lib/simple_invoice/line_items.rb +40 -0
  28. data/lib/simple_invoice/services.rb +10 -0
  29. data/lib/simple_invoice/services/allocate_invoice_number.rb +33 -0
  30. data/lib/simple_invoice/services/create_invoice.rb +50 -0
  31. data/lib/simple_invoice/services/create_invoice_for_subscription.rb +53 -0
  32. data/lib/simple_invoice/services/create_invoice_template.rb +33 -0
  33. data/lib/simple_invoice/services/process_subscription.rb +62 -0
  34. data/lib/simple_invoice/services/process_subscriptions.rb +16 -0
  35. data/lib/simple_invoice/subscription.rb +26 -0
  36. data/lib/simple_invoice/version.rb +3 -0
  37. data/simple_invoice.gemspec +20 -0
  38. data/spec/model/billing_period_spec.rb +28 -0
  39. data/spec/model/billing_period_type_monthly_spec.rb +77 -0
  40. data/spec/model/billing_period_type_weekly_spec.rb +71 -0
  41. data/spec/model/invoice_data_spec.rb +45 -0
  42. data/spec/model/line_item_spec.rb +15 -0
  43. data/spec/model/line_items_spec.rb +15 -0
  44. data/spec/model/subscription_spec.rb +28 -0
  45. data/spec/services/create_invoice_for_subscription_spec.rb +52 -0
  46. data/spec/services/process_subscription_spec.rb +42 -0
  47. data/spec/spec_helper.rb +8 -0
  48. data/test-examples.rb +23 -0
  49. data/test.sh +4 -0
  50. metadata +110 -0
@@ -0,0 +1,48 @@
1
+ module SimpleInvoice
2
+ class InvoiceData
3
+
4
+ attr_reader :invoice_number, :issue_date, :due_date
5
+
6
+ # @param inv_number [#to_s, nil]
7
+ # @param issue_date [#to_s, Date, nil]
8
+ # @param due_date_or_due_days [#to_s, Date, Fixnum, nil] interpreted as due days if Fixnum
9
+ def initialize inv_number=nil, issue_date=nil, due_date_or_due_days=nil
10
+ self.invoice_number = inv_number unless inv_number.nil?
11
+ set_dates(issue_date, due_date_or_due_days) unless [issue_date, due_date_or_due_days].any?(&:nil?)
12
+ end
13
+
14
+ # @param inv_number [#to_s]
15
+ def invoice_number= inv_number
16
+ @invoice_number = inv_number.to_s
17
+ end
18
+
19
+ # @param issue_date [#to_s, Date]
20
+ # @param due_date_or_due_days [#to_s, Date, Fixnum] interpreted as due days if Fixnum
21
+ def set_dates issue_date, due_date_or_due_days
22
+ @issue_date = to_date issue_date
23
+ if due_date_or_due_days.is_a? Fixnum
24
+ @due_date = @issue_date + due_date_or_due_days
25
+ else
26
+ @due_date = to_date due_date_or_due_days
27
+ end
28
+ end
29
+
30
+ # @return [Fixnum]
31
+ def due_days
32
+ (due_date - issue_date).to_i
33
+ end
34
+
35
+ private
36
+
37
+ # @param [String, Date] date
38
+ # @return [Date]
39
+ def to_date date
40
+ if date.is_a? Date
41
+ date
42
+ else
43
+ Date.parse date.to_s
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,15 @@
1
+ module SimpleInvoice
2
+ class InvoiceTemplate
3
+
4
+ extend Forwardable
5
+
6
+ attr_reader :line_items
7
+ attr_reader :data
8
+ def_delegator :@line_items, :push, :add_line_item
9
+
10
+ def initialize
11
+ @line_items = LineItems.new
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ module SimpleInvoice
2
+ class LineItem < Struct.new(:description, :price, :quantity)
3
+
4
+ # @return [Fixnum]
5
+ def total
6
+ price * quantity
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,40 @@
1
+ module SimpleInvoice
2
+ class LineItems
3
+
4
+ # @param line_items [Array<LineItem>]
5
+ def initialize line_items=[]
6
+ @line_items = line_items
7
+ end
8
+
9
+ # @param line_item [LineItem]
10
+ def push line_item
11
+ @line_items.push line_item
12
+ end
13
+
14
+ def length
15
+ @line_items.length
16
+ end
17
+
18
+ def each &block
19
+ @line_items.each(&block)
20
+ end
21
+
22
+ def to_a
23
+ @line_items
24
+ end
25
+
26
+ # @return [Fixnum]
27
+ def total
28
+ sum @line_items.collect(&:total)
29
+ end
30
+
31
+ private
32
+
33
+ # @param values [Array<Fixnum>]
34
+ # @return [Finxum]
35
+ def sum values
36
+ values.inject(0){|a,b| a + b }
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,10 @@
1
+ module SimpleInvoice
2
+ module Services
3
+ autoload :CreateInvoiceForSubscription, 'simple_invoice/services/create_invoice_for_subscription'
4
+ autoload :CreateInvoice, 'simple_invoice/services/create_invoice'
5
+ autoload :CreateInvoiceTemplate, 'simple_invoice/services/create_invoice_template'
6
+ autoload :AllocateInvoiceNumber, 'simple_invoice/services/allocate_invoice_number'
7
+ autoload :ProcessSubscription, 'simple_invoice/services/process_subscription'
8
+ autoload :ProcessSubscriptions, 'simple_invoice/services/process_subscriptions'
9
+ end
10
+ end
@@ -0,0 +1,33 @@
1
+ module SimpleInvoice
2
+ module Services
3
+ class AllocateInvoiceNumber
4
+
5
+ # Allocate invoice number and apply it to invoice object
6
+ # @param invoice [SimpleInvoice::Invoice]
7
+ # @return [String] invoice number
8
+ def self.call invoice
9
+ next_invoice_number(invoice).tap do |number|
10
+ invoice.invoice_number = number
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def self.next_invoice_number invoice
17
+ next_invoice_number_callable.call invoice
18
+ end
19
+
20
+ def self.next_invoice_number_callable
21
+ SimpleInvoice::Config[:allocate_invoice_number] || lambda do |invoice|
22
+ # This lambda is just a stub implementation
23
+ # A real implementation would be provided by the application,
24
+ # and not this library.
25
+ @next_invoice_number ||= 0
26
+ number = @next_invoice_number += 1
27
+ "INV-#{number}"
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,50 @@
1
+ module SimpleInvoice
2
+ module Services
3
+ class CreateInvoice
4
+
5
+ # @return [SimpleInvoice::Invoice]
6
+ def self.call &block
7
+ instance = new
8
+ block.call instance
9
+ instance.create_invoice
10
+ end
11
+
12
+ attr_writer :contact
13
+
14
+ extend Forwardable
15
+ def_delegator :invoice, :set_dates
16
+
17
+ # @return [SimpleInvoice::Invoice]
18
+ def create_invoice
19
+ invoice.tap do |inv|
20
+ allocate_invoice_number inv
21
+ end
22
+ end
23
+
24
+ # @param description [String]
25
+ # @param price [Fixnum]
26
+ # @param quantity [Fixnum]
27
+ def add_item description, price, quantity=1
28
+ line_item = LineItem.new description, price, quantity
29
+ invoice.add_line_item line_item
30
+ end
31
+
32
+ private
33
+
34
+ def invoice
35
+ @invoice ||= Invoice.new.tap do |inv|
36
+ inv.contact = contact
37
+ end
38
+ end
39
+
40
+ def allocate_invoice_number invoice
41
+ AllocateInvoiceNumber.call invoice
42
+ end
43
+
44
+ def contact
45
+ @contact ||= Contact.new
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,53 @@
1
+ module SimpleInvoice
2
+ module Services
3
+ class CreateInvoiceForSubscription
4
+
5
+ # @param subscription [SimpleInvoice::Subscription]
6
+ # @param issue_date [Date]
7
+ def initialize subscription, issue_date
8
+ @subscription = subscription
9
+ @issue_date = issue_date
10
+ end
11
+
12
+ # @return [SimpleInvoice::Invoice]
13
+ def create_invoice
14
+ invoice
15
+ end
16
+
17
+ private
18
+
19
+ # @return [SimpleInvoice::InvoiceTemplate]
20
+ def template
21
+ @subscription.invoice_template
22
+ end
23
+
24
+ # @return [SimpleInvoice::Contact]
25
+ def contact
26
+ @subscription.contact
27
+ end
28
+
29
+ # @return [SimpleInvoice::Invoice]
30
+ def invoice
31
+ @invoice ||= Invoice.new.tap do |invoice|
32
+ invoice.contact = contact
33
+ template.line_items.each do |template_line_item|
34
+ invoice.add_line_item template_line_item.dup
35
+ end
36
+ invoice.set_dates invoice_data.issue_date, invoice_data.due_date
37
+ allocate_invoice_number invoice
38
+ end
39
+ end
40
+
41
+ # @param issue_date [Date]
42
+ # @return [SimpleInvoice::InvoiceData]
43
+ def invoice_data
44
+ @invoice_data ||= InvoiceData.new nil, @issue_date, @subscription.due_days
45
+ end
46
+
47
+ def allocate_invoice_number invoice
48
+ AllocateInvoiceNumber.call invoice
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ module SimpleInvoice
2
+ module Services
3
+ class CreateInvoiceTemplate
4
+
5
+ # @return [SimpleInvoice::InvoiceTemplate]
6
+ def self.call &block
7
+ instance = new
8
+ block.call instance
9
+ instance.create_invoice_template
10
+ end
11
+
12
+ # @return [SimpleInvoice::InvoiceTemplate]
13
+ def create_invoice_template
14
+ invoice_template
15
+ end
16
+
17
+ # @param description [String]
18
+ # @param price [Fixnum]
19
+ # @param quantity [Fixnum]
20
+ def add_item description, price, quantity=1
21
+ line_item = LineItem.new description, price, quantity
22
+ invoice_template.add_line_item line_item
23
+ end
24
+
25
+ private
26
+
27
+ def invoice_template
28
+ @invoice_template ||= InvoiceTemplate.new
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,62 @@
1
+ module SimpleInvoice
2
+ module Services
3
+ class ProcessSubscription
4
+
5
+ # @param date [Date, String]
6
+ # @param subscription [SimpleInvoice::Subscription]
7
+ # @return [nil, SimpleInvoice::Invoice]
8
+ def self.call date, subscription
9
+ new(date, subscription).maybe_invoice
10
+ end
11
+
12
+ # @return [nil, SimpleInvoice::Invoice]
13
+ def maybe_invoice
14
+ if should_create_an_invoice_today?
15
+ create_invoice
16
+ else
17
+ nil
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ # @param date [Date, String]
24
+ # @param subscription [SimpleInvoice::Subscription]
25
+ def initialize date, subscription
26
+ @date = to_date date
27
+ @subscription = subscription
28
+ end
29
+
30
+ private
31
+
32
+ def should_create_an_invoice_today?
33
+ billing_periods = @subscription.billing_periods
34
+ catch(:break_with_return_value) do
35
+ loop do
36
+ billing_period = billing_periods.next
37
+ if billing_period.first_day == @date
38
+ throw :break_with_return_value, true
39
+ elsif billing_period.first_day > @date
40
+ throw :break_with_return_value, false
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def create_invoice
47
+ CreateInvoiceForSubscription.new(@subscription, @date).create_invoice
48
+ end
49
+
50
+ # @param [String, Date] date
51
+ # @return [Date]
52
+ def to_date date
53
+ if date.is_a? Date
54
+ date
55
+ else
56
+ Date.parse date.to_s
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,16 @@
1
+ module SimpleInvoice
2
+ module Services
3
+ class ProcessSubscriptions
4
+
5
+ # @param date [Date, String]
6
+ # @param subscriptions [Array<SimpleInvoice::Subscription>]
7
+ # @return [Array<SimpleInvoice::Invoice>]
8
+ def self.call date, subscriptions
9
+ subscriptions.collect do |sub|
10
+ ProcessSubscription.call date, sub
11
+ end.compact
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module SimpleInvoice
2
+ class Subscription
3
+
4
+ attr_accessor :contact, :invoice_template, :due_days, :start_date, :billing_period_type, :cancelled
5
+
6
+ def cancel!
7
+ @cancelled = true
8
+ end
9
+
10
+ def cancelled?
11
+ !!@cancelled
12
+ end
13
+
14
+ # @return [Enumerator]
15
+ def billing_periods
16
+ Enumerator.new do |y|
17
+ period = BillingPeriod.new billing_period_type, start_date
18
+ loop do
19
+ y.yield period
20
+ period = period.next_billing_period
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleInvoice
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "simple_invoice/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "simple_invoice"
7
+ s.version = SimpleInvoice::VERSION
8
+ s.authors = ["Joel Plane"]
9
+ s.email = ["joel.plane@gmail.com"]
10
+ s.homepage = "https://github.com/joelplane/simple_invoice"
11
+ s.summary = %q{simple invoice library}
12
+ s.description = %q{Very simple invoice library with minimal dependencies}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_development_dependency "rspec"
20
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ module SimpleInvoice
4
+ describe BillingPeriod do
5
+
6
+ subject { BillingPeriod.new :monthly, '2013-09-28' }
7
+
8
+ describe "#first_day" do
9
+ specify do
10
+ subject.first_day.should == Date.parse('2013-09-28')
11
+ end
12
+ end
13
+
14
+ describe "#last_day" do
15
+ specify do
16
+ subject.last_day.should == Date.parse('2013-10-27')
17
+ end
18
+ end
19
+
20
+ describe "#next_billing_period" do
21
+ specify do
22
+ subject.next_billing_period.first_day.should == Date.parse('2013-10-28')
23
+ subject.next_billing_period.last_day.should == Date.parse('2013-11-27')
24
+ end
25
+ end
26
+
27
+ end
28
+ end