borutus 0.2.1 → 0.2.2

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.
@@ -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",