uomi 0.2.13
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 +6 -0
- data/.rvmrc +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +10 -0
- data/README.md +35 -0
- data/Rakefile +8 -0
- data/config.ru +7 -0
- data/invoicing.gemspec +27 -0
- data/lib/generators/active_record/invoicing_generator.rb +30 -0
- data/lib/generators/active_record/templates/migration.rb +84 -0
- data/lib/invoicing/buyer.rb +10 -0
- data/lib/invoicing/credit_note.rb +70 -0
- data/lib/invoicing/credit_note_credit_transaction.rb +6 -0
- data/lib/invoicing/credit_note_invoice.rb +6 -0
- data/lib/invoicing/credit_transaction.rb +9 -0
- data/lib/invoicing/debit_transaction.rb +9 -0
- data/lib/invoicing/exception.rb +4 -0
- data/lib/invoicing/invoice.rb +233 -0
- data/lib/invoicing/invoice_adjustment.rb +59 -0
- data/lib/invoicing/invoice_decorator.rb +6 -0
- data/lib/invoicing/invoiceable.rb +22 -0
- data/lib/invoicing/late_payment.rb +11 -0
- data/lib/invoicing/line_item.rb +17 -0
- data/lib/invoicing/line_item_type.rb +6 -0
- data/lib/invoicing/overdue_invoice.rb +16 -0
- data/lib/invoicing/payment_reference.rb +10 -0
- data/lib/invoicing/seller.rb +10 -0
- data/lib/invoicing/transaction.rb +13 -0
- data/lib/invoicing/version.rb +3 -0
- data/lib/invoicing.rb +46 -0
- data/spec/README +0 -0
- data/spec/internal/config/database.yml.sample +3 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/schema.rb +81 -0
- data/spec/internal/log/.gitignore +1 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/lib/invoicing/credit_note_spec.rb +140 -0
- data/spec/lib/invoicing/invoice_adjustment_spec.rb +136 -0
- data/spec/lib/invoicing/invoice_late_payment_spec.rb +10 -0
- data/spec/lib/invoicing/invoice_spec.rb +419 -0
- data/spec/lib/invoicing/line_item_spec.rb +14 -0
- data/spec/lib/invoicing/overdue_invoice_spec.rb +67 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/helpers.rb +20 -0
- 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,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
|
+
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
|
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,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
|