uomi 0.2.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +6 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +10 -0
  5. data/README.md +35 -0
  6. data/Rakefile +8 -0
  7. data/config.ru +7 -0
  8. data/invoicing.gemspec +27 -0
  9. data/lib/generators/active_record/invoicing_generator.rb +30 -0
  10. data/lib/generators/active_record/templates/migration.rb +84 -0
  11. data/lib/invoicing/buyer.rb +10 -0
  12. data/lib/invoicing/credit_note.rb +70 -0
  13. data/lib/invoicing/credit_note_credit_transaction.rb +6 -0
  14. data/lib/invoicing/credit_note_invoice.rb +6 -0
  15. data/lib/invoicing/credit_transaction.rb +9 -0
  16. data/lib/invoicing/debit_transaction.rb +9 -0
  17. data/lib/invoicing/exception.rb +4 -0
  18. data/lib/invoicing/invoice.rb +233 -0
  19. data/lib/invoicing/invoice_adjustment.rb +59 -0
  20. data/lib/invoicing/invoice_decorator.rb +6 -0
  21. data/lib/invoicing/invoiceable.rb +22 -0
  22. data/lib/invoicing/late_payment.rb +11 -0
  23. data/lib/invoicing/line_item.rb +17 -0
  24. data/lib/invoicing/line_item_type.rb +6 -0
  25. data/lib/invoicing/overdue_invoice.rb +16 -0
  26. data/lib/invoicing/payment_reference.rb +10 -0
  27. data/lib/invoicing/seller.rb +10 -0
  28. data/lib/invoicing/transaction.rb +13 -0
  29. data/lib/invoicing/version.rb +3 -0
  30. data/lib/invoicing.rb +46 -0
  31. data/spec/README +0 -0
  32. data/spec/internal/config/database.yml.sample +3 -0
  33. data/spec/internal/config/routes.rb +3 -0
  34. data/spec/internal/db/schema.rb +81 -0
  35. data/spec/internal/log/.gitignore +1 -0
  36. data/spec/internal/public/favicon.ico +0 -0
  37. data/spec/lib/invoicing/credit_note_spec.rb +140 -0
  38. data/spec/lib/invoicing/invoice_adjustment_spec.rb +136 -0
  39. data/spec/lib/invoicing/invoice_late_payment_spec.rb +10 -0
  40. data/spec/lib/invoicing/invoice_spec.rb +419 -0
  41. data/spec/lib/invoicing/line_item_spec.rb +14 -0
  42. data/spec/lib/invoicing/overdue_invoice_spec.rb +67 -0
  43. data/spec/spec_helper.rb +20 -0
  44. data/spec/support/helpers.rb +20 -0
  45. metadata +185 -0
@@ -0,0 +1,17 @@
1
+ module Invoicing
2
+ class LineItem < ActiveRecord::Base
3
+ belongs_to :invoice
4
+ belongs_to :invoiceable, polymorphic: true
5
+ belongs_to :line_item_type
6
+
7
+ validates :amount, numericality: {greater_than_or_equal_to: 0}, presence: true
8
+
9
+ def net_amount
10
+ amount - tax
11
+ end
12
+
13
+ def self.for(item)
14
+ where(id: item.id).first
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ module Invoicing
2
+ class LineItemType < ActiveRecord::Base
3
+ has_many :line_items
4
+
5
+ end
6
+ end
@@ -0,0 +1,16 @@
1
+ module Invoicing
2
+ class OverdueInvoice
3
+
4
+ def self.all
5
+ Invoice.owing.select{|invoice| invoice.overdue?}
6
+ end
7
+
8
+ def self.record_late_payments!
9
+ OverdueInvoice.all.each do |invoice|
10
+ invoice.late_payment = LatePayment.new amount: invoice.balance.abs
11
+ invoice.save!
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ module Invoicing
2
+
3
+ class PaymentReference < ActiveRecord::Base
4
+
5
+ belongs_to :invoice
6
+
7
+
8
+ end
9
+
10
+ end
@@ -0,0 +1,10 @@
1
+ module Invoicing
2
+ class Seller < ActiveRecord::Base
3
+ has_many :invoices
4
+ belongs_to :sellerable, polymorphic: true
5
+
6
+ def self.for(sellerable)
7
+ Seller.where(sellerable_type: sellerable.class.name, sellerable_id: sellerable.id).first || Seller.create!(sellerable_type: sellerable.class.name, sellerable_id: sellerable.id)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Invoicing
2
+ class Transaction < ActiveRecord::Base
3
+ belongs_to :invoice
4
+
5
+ def debit?
6
+ false
7
+ end
8
+
9
+ def credit?
10
+ false
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Invoicing
2
+ VERSION = "0.2.13"
3
+ end
data/lib/invoicing.rb ADDED
@@ -0,0 +1,46 @@
1
+ require "active_record"
2
+ require "workflow"
3
+ require "invoicing/version"
4
+
5
+ module Invoicing
6
+
7
+ def self.table_name_prefix
8
+ 'invoicing_'
9
+ end
10
+
11
+ def self.generate(&block)
12
+ invoice = Invoice.new
13
+ invoice.instance_eval(&block)
14
+ invoice.save!
15
+ invoice
16
+ end
17
+
18
+ def self.generate_credit_note(&block)
19
+ credit_note = CreditNote.new
20
+ credit_note.instance_eval(&block)
21
+ credit_note.save!
22
+ credit_note.issue!
23
+ credit_note
24
+ end
25
+
26
+ end
27
+
28
+ require "invoicing/exception"
29
+ require "invoicing/invoiceable"
30
+ require "invoicing/transaction"
31
+ require "invoicing/credit_transaction"
32
+ require "invoicing/debit_transaction"
33
+ require "invoicing/late_payment"
34
+ require "invoicing/line_item"
35
+ require "invoicing/payment_reference"
36
+ require "invoicing/seller"
37
+ require "invoicing/buyer"
38
+ require "invoicing/invoice_decorator"
39
+ require "invoicing/credit_note_invoice"
40
+ require "invoicing/credit_note_credit_transaction"
41
+
42
+ require "invoicing/invoice"
43
+ require "invoicing/credit_note"
44
+ require "invoicing/overdue_invoice"
45
+ require "invoicing/invoice_adjustment"
46
+ require "invoicing/line_item_type"
data/spec/README ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: db/invoicing_test.sqlite
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ #
3
+ end
@@ -0,0 +1,81 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table "invoicing_late_payments", :force => true do |t|
4
+ t.integer "invoice_id"
5
+ t.integer "amount"
6
+ t.datetime "penalty_date"
7
+ t.boolean "processed"
8
+ t.timestamps
9
+ end
10
+
11
+ create_table "invoicing_line_items", :force => true do |t|
12
+ t.integer "invoice_id"
13
+ t.string "description"
14
+ t.integer "amount"
15
+ t.integer "tax"
16
+ t.integer "invoiceable_id"
17
+ t.string "invoiceable_type"
18
+ t.integer "line_item_type_id"
19
+ t.timestamps
20
+ end
21
+
22
+ create_table "invoicing_transactions", :force => true do |t|
23
+ t.integer "invoice_id"
24
+ t.string "type"
25
+ t.integer "amount"
26
+ t.timestamps
27
+ end
28
+
29
+ create_table "invoicing_invoices", :force => true do |t|
30
+ t.integer "seller_id"
31
+ t.integer "buyer_id"
32
+ t.string "invoice_number"
33
+ t.datetime "due_date"
34
+ t.datetime "issued_at"
35
+ t.integer "total"
36
+ t.integer "tax"
37
+ t.integer "balance"
38
+ t.string "type"
39
+ t.string "workflow_state"
40
+ t.timestamps
41
+ end
42
+
43
+ create_table "invoicing_payment_references", :force => true do |t|
44
+ t.integer "invoice_id"
45
+ t.string "reference"
46
+ t.timestamps
47
+ end
48
+
49
+ create_table "invoicing_sellers", :force => true do |t|
50
+ t.integer "sellerable_id"
51
+ t.string "sellerable_type"
52
+ t.timestamps
53
+ end
54
+
55
+ create_table "invoicing_buyers", :force => true do |t|
56
+ t.integer "buyerable_id"
57
+ t.string "buyerable_type"
58
+ t.timestamps
59
+ end
60
+
61
+ create_table "invoicing_invoice_decorators", :force => true do |t|
62
+ t.integer "invoice_id"
63
+ t.text "data"
64
+ t.timestamps
65
+ end
66
+
67
+ create_table "invoicing_credit_note_invoices", :force => true do |t|
68
+ t.integer "invoice_id"
69
+ t.integer "credit_note_id"
70
+ end
71
+
72
+ create_table "invoicing_credit_note_credit_transactions", :force => true do |t|
73
+ t.integer "credit_note_id"
74
+ t.integer "transaction_id"
75
+ end
76
+
77
+ create_table "invoicing_line_item_types", :force => true do |t|
78
+ t.string "name"
79
+ end
80
+
81
+ end
@@ -0,0 +1 @@
1
+ *.log
File without changes
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+
3
+ describe Invoicing::CreditNote do
4
+ include Helpers
5
+
6
+ before(:each) do
7
+ tear_it_down
8
+
9
+ invoice_buyer = Invoicing::DebitTransaction.create!
10
+ invoice_seller = Invoicing::DebitTransaction.create!
11
+
12
+ @invoice = Invoicing::generate do
13
+ to invoice_buyer
14
+ from invoice_seller
15
+
16
+ line_item description: "Line Item 1", amount: 1101
17
+ line_item description: "Line Item 2", amount: 5097
18
+ line_item description: "Line Item 3", amount: 1714
19
+ line_item description: "Line Item 4", amount: 20300
20
+
21
+ payment_reference "REF123"
22
+ decorate_with tenant_name: "Peter"
23
+ end
24
+
25
+ @invoice_buyer = invoice_buyer
26
+ @invoice_seller = invoice_seller
27
+
28
+ @invoice.issue!
29
+ end
30
+
31
+ context "when recording a credit note" do
32
+ before(:each) do
33
+ @invoice.line_items.each do |li|
34
+ li.invoiceable = li
35
+ li.invoiceable.extend(Invoicing::Invoiceable)
36
+ li.amount = 0
37
+ li.tax = 0
38
+ li.save!
39
+ end
40
+
41
+ @invoice.reload
42
+ end
43
+
44
+ it "should raise an exception if the invoice is not in an issued state" do
45
+ lambda {
46
+ @credit_note = Invoicing::generate_credit_note do
47
+ against_invoice @invoice
48
+ end
49
+ }.should raise_error(RuntimeError, "You must allocate a credit note against an invoice")
50
+
51
+ end
52
+
53
+ context "against an invoice" do
54
+ before(:each) do
55
+ invoice = @invoice
56
+ line_items = invoice.line_items
57
+
58
+ @credit_note = Invoicing::generate_credit_note do
59
+ line_items.each do |line_item|
60
+ credit amount: line_item.amount, line_item: line_item, description: "Credit note for Line Item #{line_item.id}", tax: 0
61
+ end
62
+
63
+ against_invoice invoice
64
+ end
65
+
66
+ @invoice.reload
67
+ end
68
+
69
+ it "should be issued" do
70
+ @credit_note.should be_issued
71
+ end
72
+
73
+ it "should have a receipt number" do
74
+ @credit_note.receipt_number.should == "CN#{@credit_note.id}"
75
+ end
76
+
77
+ it "should be able to record a line item for the credit note" do
78
+ @credit_note.line_items.count.should == @invoice.line_items.count
79
+ @credit_note.line_items.first.amount.should == @invoice.line_items.first.amount
80
+ end
81
+
82
+ it "should create a credit transaction against the invoice" do
83
+ @invoice.settled?.should be_true
84
+ end
85
+
86
+ it "should know if it is linked to an invoice" do
87
+ @credit_note.invoice.should == @invoice
88
+ end
89
+
90
+ it "should be linked to the credit transaction paid against the invoice" do
91
+ @credit_note.credit_note_credit_transactions.count.should == 1
92
+ @credit_note.credit_note_credit_transactions.first.transaction.amount.should == @invoice.total
93
+ end
94
+
95
+ it "should be recorded against the invoice" do
96
+ @invoice.reload
97
+ @invoice.credit_notes.count.should == 1
98
+ @invoice.credit_notes.first.receipt_number.should == "CN#{@credit_note.id}"
99
+ end
100
+
101
+ it "should set the buyer to the invoice's buyer" do
102
+ @credit_note.buyer.buyerable.should == @invoice_buyer
103
+ end
104
+
105
+ it "should set the seller to the invoice's seller" do
106
+ @credit_note.seller.sellerable.should == @invoice_seller
107
+ end
108
+
109
+ end
110
+
111
+ context "When applying credits to the invoiceables" do
112
+
113
+ it "should receive handle credit for each line item being credited" do
114
+ credit_note = Invoicing::CreditNote.new
115
+ credit_note.line_items = @invoice.line_items
116
+
117
+ credit_note.line_items.map(&:invoiceable).compact.each do |item|
118
+ item.should_receive(:handle_credit)
119
+ end
120
+
121
+ credit_note.record_credit_notes!
122
+ end
123
+
124
+ end
125
+
126
+ context "trying to create a standalone credit note" do
127
+
128
+ it "should raise an error if no invoice is specified" do
129
+ expect {Invoicing::generate_credit_note do
130
+ line_items.each do |line_item|
131
+ credit amount: 500, description: "Credit note for Line Item #{line_item.id}", tax: 0
132
+ end
133
+ decorate_with tenant_name: "Peter"
134
+ end}.to raise_error(RuntimeError, "You must allocate a credit note against an invoice")
135
+ end
136
+ end
137
+
138
+ end
139
+
140
+ end
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+
3
+ describe Invoicing::InvoiceAdjustment do
4
+ include Helpers
5
+
6
+ before(:each) do
7
+ tear_it_down
8
+
9
+ buyer = Invoicing::DebitTransaction.create!
10
+
11
+ @invoice = Invoicing::generate do
12
+ to buyer
13
+ numbered "INV123"
14
+
15
+ line_item description: "Line Item 1", amount: 1101, line_item_type_id: 1
16
+ line_item description: "Line Item 2", amount: 5097, line_item_type_id: 1
17
+ line_item description: "Line Item 3", amount: 1714, line_item_type_id: 1
18
+
19
+ payment_reference "REF123"
20
+ decorate_with tenant_name: "Peter"
21
+ end
22
+ end
23
+
24
+ context "adjusting a draft invoice" do
25
+
26
+ it "should allow the due date to be updated" do
27
+ @invoice.adjust do
28
+ due Date.tomorrow
29
+ end
30
+ end
31
+
32
+ it "should allow the buyer to be changed" do
33
+ new_buyer = Invoicing::DebitTransaction.create!
34
+
35
+ @invoice.adjust do
36
+ to new_buyer
37
+ end
38
+
39
+ @invoice.buyer.buyerable.should == new_buyer
40
+ end
41
+
42
+ it "should allow a payment references to be added" do
43
+ @invoice.adjust do
44
+ add_payment_reference "NEWREF123"
45
+ end
46
+
47
+ @invoice.payment_references.count.should == 2
48
+ end
49
+
50
+ it "should allow a payment references to be removed" do
51
+ @invoice.adjust do
52
+ remove_payment_reference @invoice.payment_references.last
53
+ end
54
+
55
+ @invoice.payment_references.count.should == 0
56
+ end
57
+
58
+ it "should allow the invoice decorator to be updated" do
59
+ decorations = {tenant_name: "Bob"}
60
+
61
+ @invoice.adjust do
62
+ decorate_with decorations
63
+ end
64
+
65
+ # @invoice.reload
66
+
67
+ @invoice.decorator.data.should == decorations
68
+ end
69
+
70
+ it "should allow line items to be added" do
71
+ item_to_invoice = @invoice.extend(Invoicing::Invoiceable)
72
+ @invoice.line_items.count.should == 3
73
+
74
+ @invoice.adjust do
75
+ add_line_item(item_to_invoice)
76
+ end
77
+
78
+ @invoice.line_items.count.should == 4
79
+ @invoice.total.should == 7912
80
+ end
81
+
82
+ it "should allow line items to be edited" do
83
+ line_item = @invoice.line_items.first
84
+ item_to_invoice = @invoice.extend(Invoicing::Invoiceable)
85
+
86
+ @invoice.adjust do
87
+ edit_line_item(line_item, {description: "Modified Line Item", amount: 500})
88
+ end
89
+
90
+ @invoice.line_items.count.should == 3
91
+ @invoice.total.should == 7311
92
+
93
+ line_item.reload
94
+
95
+ line_item.description.should == "Modified Line Item"
96
+ line_item.amount.should == 500
97
+ end
98
+
99
+ it "should allow line items to be removed" do
100
+ @invoice.line_items.count.should == 3
101
+
102
+ @invoice.adjust do
103
+ remove_line_item(@invoice.line_items.first)
104
+ end
105
+
106
+ @invoice.line_items.count.should == 2
107
+ @invoice.total.should == 6811
108
+ end
109
+
110
+ end
111
+
112
+ context "Adjusting an Invoice Number" do
113
+ it "should allow the invoice number to be updated" do
114
+ @invoice.adjust do
115
+ numbered "DRAFT-INV123"
116
+ end
117
+
118
+ @invoice.invoice_number.should == "DRAFT-INV123"
119
+ end
120
+ end
121
+
122
+ context "adjusting an issued invoice" do
123
+
124
+ it "should raise an exception" do
125
+ @invoice.issue!
126
+
127
+ expect {
128
+ @invoice.adjust do
129
+ due Date.tomorrow
130
+ end
131
+ }.to raise_error(Invoicing::CannotAdjustIssuedDocument)
132
+ end
133
+
134
+ end
135
+
136
+ end
@@ -0,0 +1,10 @@
1
+ # require 'spec_helper'
2
+ #
3
+ # describe Invoicing::LatePayment do
4
+ # include Helpers
5
+ #
6
+ # before(:each) do
7
+ # tear_it_down
8
+ # end
9
+ #
10
+ # end