borutus 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,19 @@
1
1
  module Borutus
2
+
2
3
  # The Amount class represents debit and credit amounts in the system.
3
4
  #
4
5
  # @abstract
5
- # An amount must be a subclass as either a debit or a credit to be saved to the database.
6
+ # An amount must be a subclass as either a debit or a credit to be saved
7
+ # to the database.
6
8
  #
7
9
  # @author Michael Bulat
8
10
  class Amount < ActiveRecord::Base
9
- belongs_to :entry, :class_name => 'Borutus::Entry'
10
- belongs_to :account, :class_name => 'Borutus::Account'
11
+
12
+ belongs_to :entry, class_name: "Borutus::Entry"
13
+ belongs_to(:account, {
14
+ class_name: "Borutus::Account",
15
+ counter_cache: :borutus_amounts_count,
16
+ })
11
17
 
12
18
  validates_presence_of :type, :amount, :entry, :account
13
19
  # attr_accessible :account, :account_name, :amount, :entry
@@ -19,6 +25,5 @@ module Borutus
19
25
  self.account = Account.find_by_name!(name)
20
26
  end
21
27
 
22
- protected
23
28
  end
24
29
  end
@@ -30,7 +30,7 @@ module Borutus
30
30
  #
31
31
  # Since this does not use the database for sumation, it may be used on non-persisted records.
32
32
  def balance_for_new_record
33
- balance = BigDecimal.new('0')
33
+ balance = BigDecimal('0')
34
34
  each do |amount_record|
35
35
  if amount_record.amount && !amount_record.marked_for_destruction?
36
36
  balance += amount_record.amount # unless amount_record.marked_for_destruction?
@@ -0,0 +1,73 @@
1
+ module Borutus
2
+ module Accounts
3
+ class BuildRunningBalanceQuery
4
+
5
+ STATEMENTS = {
6
+ coalesce_debit: %{ COALESCE("debit_table"."amount", 0) },
7
+ coalesce_credit: %{ COALESCE("credit_table"."amount", 0) },
8
+ amount_sum: %{ SUM("borutus_amounts".amount) AS amount },
9
+ }.freeze
10
+
11
+ extend LightService::Action
12
+
13
+ expects :account
14
+ promises :joins_statement, :select_statement, :group_statement
15
+
16
+ executed do |c|
17
+ account = c.account
18
+ credit_amounts = account.credit_amounts
19
+ debit_amounts = account.debit_amounts
20
+
21
+ credit_table = credit_amounts.joins(:entry).select(
22
+ :id,
23
+ :entry_id,
24
+ STATEMENTS[:amount_sum],
25
+ ).group(:entry_id, :id)
26
+
27
+ debit_table = debit_amounts.joins(:entry).select(
28
+ :id,
29
+ :entry_id,
30
+ STATEMENTS[:amount_sum],
31
+ ).group(:entry_id, :id)
32
+
33
+ sum_statement = sum_statement_from(account)
34
+
35
+ c.joins_statement = joins_statement_from(credit_table, debit_table)
36
+ c.select_statement = select_statement_from(sum_statement)
37
+ c.group_statement = group_statement_from
38
+ end
39
+
40
+ def self.sum_statement_from(account)
41
+ if account.normal_credit_balance
42
+ %( #{STATEMENTS[:coalesce_credit]} - #{STATEMENTS[:coalesce_debit]} )
43
+ else
44
+ %( #{STATEMENTS[:coalesce_debit]} - #{STATEMENTS[:coalesce_credit]} )
45
+ end
46
+ end
47
+ private_class_method :sum_statement_from
48
+
49
+ def self.joins_statement_from(credit_table, debit_table)
50
+ %{
51
+ LEFT OUTER JOIN (#{credit_table.to_sql}) AS "credit_table" ON "credit_table".entry_id = "borutus_entries".id
52
+ LEFT OUTER JOIN (#{debit_table.to_sql}) AS "debit_table" ON "debit_table".entry_id = "borutus_entries".id
53
+ }
54
+ end
55
+ private_class_method :joins_statement_from
56
+
57
+ def self.select_statement_from(sum_statement)
58
+ %{
59
+ "borutus_entries".*,
60
+ SUM(#{sum_statement}) OVER(ORDER BY "borutus_entries"."created_at") AS balance,
61
+ #{sum_statement} AS change_amount
62
+ }
63
+ end
64
+ private_class_method :select_statement_from
65
+
66
+ def self.group_statement_from
67
+ %( "borutus_entries".id, "debit_table".amount, "credit_table".amount )
68
+ end
69
+ private_class_method :group_statement_from
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,15 @@
1
+ class AddBorutusAmountCounterCache < ActiveRecord::Migration[4.2]
2
+
3
+ def up
4
+ add_column :borutus_accounts, :borutus_amounts_count, :integer
5
+
6
+ Borutus::Account.all.pluck(:id).each do |id|
7
+ Borutus::Account.reset_counters(id, :borutus_amounts)
8
+ end
9
+ end
10
+
11
+ def down
12
+ remove_column :borutus_accounts, :borutus_amounts_count
13
+ end
14
+
15
+ end
@@ -1,5 +1,6 @@
1
1
  # Borutus
2
2
  require "rails"
3
+ require "light-service"
3
4
 
4
5
  module Borutus
5
6
  # ------------------------------ tenancy ------------------------------
@@ -1,3 +1,3 @@
1
1
  module Borutus
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -5,7 +5,7 @@ module Borutus
5
5
  routes { Borutus::Engine.routes }
6
6
 
7
7
  def mock_account(stubs={})
8
- @mock_account ||= FactoryGirl.create(:asset)
8
+ @mock_account ||= FactoryBot.create(:asset)
9
9
  end
10
10
 
11
11
  describe "GET index" do
@@ -5,7 +5,7 @@ module Borutus
5
5
  routes { Borutus::Engine.routes }
6
6
 
7
7
  def mock_entry(stubs={})
8
- @mock_entry ||= FactoryGirl.create(:entry_with_credit_and_debit)
8
+ @mock_entry ||= FactoryBot.create(:entry_with_credit_and_debit)
9
9
  end
10
10
 
11
11
  describe "GET index" do
@@ -5,7 +5,7 @@ module Borutus
5
5
  routes { Borutus::Engine.routes }
6
6
 
7
7
  def mock_entry(stubs={})
8
- @mock_entry ||= FactoryGirl.create(:entry_with_credit_and_debit)
8
+ @mock_entry ||= FactoryBot.create(:entry_with_credit_and_debit)
9
9
  end
10
10
 
11
11
  describe "GET balance_sheet" do
@@ -1,32 +1,32 @@
1
- FactoryGirl.define do
2
- factory :account, :class => Borutus::Account do |account|
1
+ FactoryBot.define do
2
+ factory :account, class: Borutus::Account do |account|
3
3
  account.name
4
- account.contra false
4
+ account.contra { false }
5
5
  end
6
6
 
7
- factory :asset, :class => Borutus::Asset do |account|
7
+ factory :asset, class: Borutus::Asset do |account|
8
8
  account.name
9
- account.contra false
9
+ account.contra { false }
10
10
  end
11
11
 
12
- factory :equity, :class => Borutus::Equity do |account|
12
+ factory :equity, class: Borutus::Equity do |account|
13
13
  account.name
14
- account.contra false
14
+ account.contra { false }
15
15
  end
16
16
 
17
- factory :expense, :class => Borutus::Expense do |account|
17
+ factory :expense, class: Borutus::Expense do |account|
18
18
  account.name
19
- account.contra false
19
+ account.contra { false }
20
20
  end
21
21
 
22
- factory :liability, :class => Borutus::Liability do |account|
22
+ factory :liability, class: Borutus::Liability do |account|
23
23
  account.name
24
- account.contra false
24
+ account.contra { false }
25
25
  end
26
26
 
27
- factory :revenue, :class => Borutus::Revenue do |account|
27
+ factory :revenue, class: Borutus::Revenue do |account|
28
28
  account.name
29
- account.contra false
29
+ account.contra { false }
30
30
  end
31
31
 
32
32
  sequence :name do |n|
@@ -1,19 +1,19 @@
1
- FactoryGirl.define do
2
- factory :amount, :class => Borutus::Amount do |amount|
3
- amount.amount BigDecimal.new('473')
4
- amount.association :entry, :factory => :entry_with_credit_and_debit
5
- amount.association :account, :factory => :asset
1
+ FactoryBot.define do
2
+ factory :amount, class: Borutus::Amount do |amount|
3
+ amount.amount { BigDecimal("473") }
4
+ amount.association :entry, factory: :entry_with_credit_and_debit
5
+ amount.association :account, factory: :asset
6
6
  end
7
7
 
8
- factory :credit_amount, :class => Borutus::CreditAmount do |credit_amount|
9
- credit_amount.amount BigDecimal.new('473')
10
- credit_amount.association :entry, :factory => :entry_with_credit_and_debit
11
- credit_amount.association :account, :factory => :revenue
8
+ factory :credit_amount, class: Borutus::CreditAmount do |credit_amount|
9
+ credit_amount.amount { BigDecimal("473") }
10
+ credit_amount.association :entry, factory: :entry_with_credit_and_debit
11
+ credit_amount.association :account, factory: :revenue
12
12
  end
13
13
 
14
- factory :debit_amount, :class => Borutus::DebitAmount do |debit_amount|
15
- debit_amount.amount BigDecimal.new('473')
16
- debit_amount.association :entry, :factory => :entry_with_credit_and_debit
17
- debit_amount.association :account, :factory => :asset
14
+ factory :debit_amount, class: Borutus::DebitAmount do |debit_amount|
15
+ debit_amount.amount { BigDecimal("473") }
16
+ debit_amount.association :entry, factory: :entry_with_credit_and_debit
17
+ debit_amount.association :account, factory: :asset
18
18
  end
19
19
  end
@@ -1,11 +1,14 @@
1
- FactoryGirl.define do
2
- factory :entry, :class => Borutus::Entry do |entry|
3
- entry.description 'factory description'
4
- factory :entry_with_credit_and_debit, :class => Borutus::Entry do |entry_cd|
5
- entry_cd.after_build do |t|
6
- t.credit_amounts << FactoryGirl.build(:credit_amount, :entry => t)
7
- t.debit_amounts << FactoryGirl.build(:debit_amount, :entry => t)
8
- end
1
+ FactoryBot.define do
2
+ factory :entry, class: Borutus::Entry do
3
+ description { "factory description" }
4
+ end
5
+
6
+ factory :entry_with_credit_and_debit, class: Borutus::Entry do
7
+ description { "factory description" }
8
+
9
+ after(:build) do |entry|
10
+ entry.credit_amounts << FactoryBot.build(:credit_amount, entry: entry)
11
+ entry.debit_amounts << FactoryBot.build(:debit_amount, entry: entry)
9
12
  end
10
13
  end
11
14
  end
@@ -1,39 +1,40 @@
1
- require 'spec_helper'
1
+ require "spec_helper"
2
2
 
3
3
  module Borutus
4
4
  describe Account do
5
- let(:account) { FactoryGirl.build(:account) }
5
+
6
+ let(:account) { FactoryBot.build(:account) }
6
7
  subject { account }
7
8
 
8
- it { is_expected.not_to be_valid } # must construct a child type instead
9
+ it { is_expected.not_to be_valid } # must construct a child type instead
9
10
 
10
11
  describe ".types" do
11
12
  it "lists the available types" do
12
- expect(described_class.types).
13
- to match_array([Asset, Equity, Expense, Liability, Revenue])
13
+ expect(described_class.types)
14
+ .to match_array([Asset, Equity, Expense, Liability, Revenue])
14
15
  end
15
16
  end
16
17
 
17
18
  describe ".entries.with_running_balance" do
18
- let(:mock_document) { FactoryGirl.create(:asset) }
19
+ let(:mock_document) { FactoryBot.create(:asset) }
19
20
  let!(:accounts_receivable) do
20
- FactoryGirl.create(:asset, name: "Accounts Receivable")
21
+ FactoryBot.create(:asset, name: "Accounts Receivable")
21
22
  end
22
23
  let!(:sales_revenue) do
23
- FactoryGirl.create(:revenue, name: "Sales Revenue")
24
+ FactoryBot.create(:revenue, name: "Sales Revenue")
24
25
  end
25
26
  let!(:sales_tax_payable) do
26
- FactoryGirl.create(:liability, name: "Sales Tax Payable")
27
+ FactoryBot.create(:liability, name: "Sales Tax Payable")
27
28
  end
28
29
  let!(:entry_1) do
29
30
  Borutus::Entry.new({
30
31
  description: "Sold some widgets",
31
32
  commercial_document: mock_document,
32
- debits: [{account: accounts_receivable, amount: 50}],
33
+ debits: [{ account: accounts_receivable, amount: 50 }],
33
34
  credits: [
34
- {account: sales_revenue, amount: 45},
35
- {account: sales_tax_payable, amount: 5}
36
- ]
35
+ { account: sales_revenue, amount: 45 },
36
+ { account: sales_tax_payable, amount: 5 },
37
+ ],
37
38
  })
38
39
  end
39
40
  let!(:entry_2) do
@@ -41,20 +42,20 @@ module Borutus
41
42
  description: "Cancel Accounts receivable some widgets again",
42
43
  commercial_document: mock_document,
43
44
  debits: [
44
- {account: accounts_receivable, amount: -30},
45
+ { account: accounts_receivable, amount: -30 },
45
46
  ],
46
47
  credits: [
47
- {account: sales_revenue, amount: -25},
48
- {account: sales_tax_payable, amount: -5},
49
- ]
48
+ { account: sales_revenue, amount: -25 },
49
+ { account: sales_tax_payable, amount: -5 },
50
+ ],
50
51
  })
51
52
  end
52
53
  let!(:entry_3) do
53
54
  Borutus::Entry.new({
54
55
  description: "Cancel Accounts receivable",
55
56
  commercial_document: mock_document,
56
- debits: [{account: sales_tax_payable, amount: 15}],
57
- credits: [{account: accounts_receivable, amount: 15}],
57
+ debits: [{ account: sales_tax_payable, amount: 15 }],
58
+ credits: [{ account: accounts_receivable, amount: 15 }],
58
59
  })
59
60
  end
60
61
 
@@ -84,12 +85,56 @@ module Borutus
84
85
  end
85
86
  end
86
87
 
88
+ describe ".with_amounts" do
89
+ let(:mock_document) { FactoryBot.create(:asset) }
90
+ let!(:cash) do
91
+ FactoryBot.create(:asset, name: "Cash")
92
+ end
93
+ let!(:accounts_receivable) do
94
+ FactoryBot.create(:asset, name: "Accounts Receivable")
95
+ end
96
+ let!(:sales_revenue) do
97
+ FactoryBot.create(:revenue, name: "Sales Revenue")
98
+ end
99
+ let!(:sales_tax_payable) do
100
+ FactoryBot.create(:liability, name: "Sales Tax Payable")
101
+ end
102
+ let!(:entry_1) do
103
+ Borutus::Entry.new({
104
+ description: "Sold some widgets",
105
+ commercial_document: mock_document,
106
+ debits: [{ account: accounts_receivable, amount: 50 }],
107
+ credits: [
108
+ { account: sales_revenue, amount: 45 },
109
+ { account: sales_tax_payable, amount: 5 },
110
+ ],
111
+ })
112
+ end
113
+
114
+ it "only returns with borutus_amounts_count > 0" do
115
+ entry_1.save
116
+
117
+ accounts = Borutus::Account.with_amounts
118
+ expect(accounts.count).to eq 3
119
+
120
+ account_names = accounts.pluck(:name)
121
+
122
+ [
123
+ "Accounts Receivable",
124
+ "Sales Revenue",
125
+ "Sales Tax Payable",
126
+ ].each do |account_name|
127
+ expect(account_names).to include account_name
128
+ end
129
+ end
130
+ end
131
+
87
132
  describe "when using a child type" do
88
- let(:account) { FactoryGirl.create(:account, type: "Finance::Asset") }
133
+ let(:account) { FactoryBot.create(:account, type: "Finance::Asset") }
89
134
  it { is_expected.to be_valid }
90
135
 
91
136
  it "should be unique per name" do
92
- conflict = FactoryGirl.build(:account, name: account.name, type: account.type)
137
+ conflict = FactoryBot.build(:account, name: account.name, type: account.type)
93
138
  expect(conflict).not_to be_valid
94
139
  expect(conflict.errors[:name]).to eq(["has already been taken"])
95
140
  end
@@ -114,36 +159,36 @@ module Borutus
114
159
  context "when given correct entries" do
115
160
  before {
116
161
  # credit accounts
117
- liability = FactoryGirl.create(:liability)
118
- equity = FactoryGirl.create(:equity)
119
- revenue = FactoryGirl.create(:revenue)
120
- contra_asset = FactoryGirl.create(:asset, :contra => true)
121
- contra_expense = FactoryGirl.create(:expense, :contra => true)
162
+ liability = FactoryBot.create(:liability)
163
+ equity = FactoryBot.create(:equity)
164
+ revenue = FactoryBot.create(:revenue)
165
+ contra_asset = FactoryBot.create(:asset, contra: true)
166
+ contra_expense = FactoryBot.create(:expense, contra: true)
122
167
  # credit amounts
123
- ca1 = FactoryGirl.build(:credit_amount, :account => liability, :amount => 100000)
124
- ca2 = FactoryGirl.build(:credit_amount, :account => equity, :amount => 1000)
125
- ca3 = FactoryGirl.build(:credit_amount, :account => revenue, :amount => 40404)
126
- ca4 = FactoryGirl.build(:credit_amount, :account => contra_asset, :amount => 2)
127
- ca5 = FactoryGirl.build(:credit_amount, :account => contra_expense, :amount => 333)
168
+ ca1 = FactoryBot.build(:credit_amount, account: liability, amount: 100_000)
169
+ ca2 = FactoryBot.build(:credit_amount, account: equity, amount: 1000)
170
+ ca3 = FactoryBot.build(:credit_amount, account: revenue, amount: 40_404)
171
+ ca4 = FactoryBot.build(:credit_amount, account: contra_asset, amount: 2)
172
+ ca5 = FactoryBot.build(:credit_amount, account: contra_expense, amount: 333)
128
173
 
129
174
  # debit accounts
130
- asset = FactoryGirl.create(:asset)
131
- expense = FactoryGirl.create(:expense)
132
- contra_liability = FactoryGirl.create(:liability, :contra => true)
133
- contra_equity = FactoryGirl.create(:equity, :contra => true)
134
- contra_revenue = FactoryGirl.create(:revenue, :contra => true)
175
+ asset = FactoryBot.create(:asset)
176
+ expense = FactoryBot.create(:expense)
177
+ contra_liability = FactoryBot.create(:liability, contra: true)
178
+ contra_equity = FactoryBot.create(:equity, contra: true)
179
+ contra_revenue = FactoryBot.create(:revenue, contra: true)
135
180
  # debit amounts
136
- da1 = FactoryGirl.build(:debit_amount, :account => asset, :amount => 100000)
137
- da2 = FactoryGirl.build(:debit_amount, :account => expense, :amount => 1000)
138
- da3 = FactoryGirl.build(:debit_amount, :account => contra_liability, :amount => 40404)
139
- da4 = FactoryGirl.build(:debit_amount, :account => contra_equity, :amount => 2)
140
- da5 = FactoryGirl.build(:debit_amount, :account => contra_revenue, :amount => 333)
141
-
142
- FactoryGirl.create(:entry, :credit_amounts => [ca1], :debit_amounts => [da1])
143
- FactoryGirl.create(:entry, :credit_amounts => [ca2], :debit_amounts => [da2])
144
- FactoryGirl.create(:entry, :credit_amounts => [ca3], :debit_amounts => [da3])
145
- FactoryGirl.create(:entry, :credit_amounts => [ca4], :debit_amounts => [da4])
146
- FactoryGirl.create(:entry, :credit_amounts => [ca5], :debit_amounts => [da5])
181
+ da1 = FactoryBot.build(:debit_amount, account: asset, amount: 100_000)
182
+ da2 = FactoryBot.build(:debit_amount, account: expense, amount: 1000)
183
+ da3 = FactoryBot.build(:debit_amount, account: contra_liability, amount: 40_404)
184
+ da4 = FactoryBot.build(:debit_amount, account: contra_equity, amount: 2)
185
+ da5 = FactoryBot.build(:debit_amount, account: contra_revenue, amount: 333)
186
+
187
+ FactoryBot.create(:entry, credit_amounts: [ca1], debit_amounts: [da1])
188
+ FactoryBot.create(:entry, credit_amounts: [ca2], debit_amounts: [da2])
189
+ FactoryBot.create(:entry, credit_amounts: [ca3], debit_amounts: [da3])
190
+ FactoryBot.create(:entry, credit_amounts: [ca4], debit_amounts: [da4])
191
+ FactoryBot.create(:entry, credit_amounts: [ca5], debit_amounts: [da5])
147
192
  }
148
193
 
149
194
  it { is_expected.to eq(0) }
@@ -152,9 +197,9 @@ module Borutus
152
197
 
153
198
  describe "#amounts" do
154
199
  it "returns all credit and debit amounts" do
155
- equity = FactoryGirl.create(:equity)
156
- asset = FactoryGirl.create(:asset)
157
- expense = FactoryGirl.create(:expense)
200
+ equity = FactoryBot.create(:equity)
201
+ asset = FactoryBot.create(:asset)
202
+ expense = FactoryBot.create(:expense)
158
203
 
159
204
  investment = Entry.new(
160
205
  description: "Initial investment",
@@ -180,9 +225,9 @@ module Borutus
180
225
 
181
226
  describe "#entries" do
182
227
  it "returns all credit and debit entries" do
183
- equity = FactoryGirl.create(:equity)
184
- asset = FactoryGirl.create(:asset)
185
- expense = FactoryGirl.create(:expense)
228
+ equity = FactoryBot.create(:equity)
229
+ asset = FactoryBot.create(:asset)
230
+ expense = FactoryBot.create(:expense)
186
231
 
187
232
  investment = Entry.new(
188
233
  description: "Initial investment",