event_sourced_accounting 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +23 -0
- data/README.markdown +37 -0
- data/Rakefile +11 -0
- data/app/assets/javascripts/esa/application.js +15 -0
- data/app/assets/stylesheets/esa/application.css +13 -0
- data/app/assets/stylesheets/esa/main.css.scss +86 -0
- data/app/models/esa/account.rb +80 -0
- data/app/models/esa/accounts/asset.rb +22 -0
- data/app/models/esa/accounts/equity.rb +22 -0
- data/app/models/esa/accounts/expense.rb +22 -0
- data/app/models/esa/accounts/liability.rb +22 -0
- data/app/models/esa/accounts/revenue.rb +22 -0
- data/app/models/esa/amount.rb +27 -0
- data/app/models/esa/amounts/credit.rb +12 -0
- data/app/models/esa/amounts/debit.rb +12 -0
- data/app/models/esa/associations/amounts_extension.rb +79 -0
- data/app/models/esa/associations/events_extension.rb +33 -0
- data/app/models/esa/associations/flags_extension.rb +35 -0
- data/app/models/esa/associations/transactions_extension.rb +7 -0
- data/app/models/esa/chart.rb +41 -0
- data/app/models/esa/context.rb +218 -0
- data/app/models/esa/context_provider.rb +52 -0
- data/app/models/esa/context_providers/account_context_provider.rb +21 -0
- data/app/models/esa/context_providers/accountable_context_provider.rb +22 -0
- data/app/models/esa/context_providers/accountable_type_context_provider.rb +21 -0
- data/app/models/esa/context_providers/date_context_provider.rb +33 -0
- data/app/models/esa/contexts/account_context.rb +26 -0
- data/app/models/esa/contexts/accountable_context.rb +26 -0
- data/app/models/esa/contexts/accountable_type_context.rb +24 -0
- data/app/models/esa/contexts/created_at_context.rb +26 -0
- data/app/models/esa/contexts/date_context.rb +71 -0
- data/app/models/esa/contexts/empty_context.rb +19 -0
- data/app/models/esa/contexts/filter_context.rb +11 -0
- data/app/models/esa/contexts/open_close_context.rb +15 -0
- data/app/models/esa/event.rb +33 -0
- data/app/models/esa/filters/account_filter.rb +42 -0
- data/app/models/esa/filters/accountable_filter.rb +58 -0
- data/app/models/esa/filters/accountable_type_filter.rb +26 -0
- data/app/models/esa/filters/chart_filter.rb +17 -0
- data/app/models/esa/filters/context_filter.rb +35 -0
- data/app/models/esa/filters/date_time_filter.rb +52 -0
- data/app/models/esa/flag.rb +70 -0
- data/app/models/esa/ruleset.rb +175 -0
- data/app/models/esa/traits/accountable.rb +21 -0
- data/app/models/esa/traits/extendable.rb +93 -0
- data/app/models/esa/traits/or_scope.rb +35 -0
- data/app/models/esa/traits/union_scope.rb +24 -0
- data/app/models/esa/transaction.rb +74 -0
- data/config/backtrace_silencers.rb +7 -0
- data/config/database.yml +5 -0
- data/config/inflections.rb +10 -0
- data/config/mime_types.rb +5 -0
- data/config/routes.rb +6 -0
- data/config/secret_token.rb +7 -0
- data/config/session_store.rb +8 -0
- data/lib/esa/balance_checker.rb +17 -0
- data/lib/esa/blocking_processor.rb +158 -0
- data/lib/esa/config.rb +55 -0
- data/lib/esa/subcontext_checker.rb +15 -0
- data/lib/esa/version.rb +3 -0
- data/lib/esa.rb +8 -0
- data/lib/generators/esa/USAGE +11 -0
- data/lib/generators/esa/esa_generator.rb +26 -0
- data/lib/generators/esa/templates/migration.rb +142 -0
- data/spec/factories/account_factory.rb +51 -0
- data/spec/factories/amount_factory.rb +19 -0
- data/spec/factories/chart_factory.rb +7 -0
- data/spec/factories/transaction_factory.rb +11 -0
- data/spec/lib/esa_spec.rb +0 -0
- data/spec/models/account_spec.rb +31 -0
- data/spec/models/amount_spec.rb +8 -0
- data/spec/models/asset_spec.rb +9 -0
- data/spec/models/chart_spec.rb +52 -0
- data/spec/models/credit_amount_spec.rb +9 -0
- data/spec/models/debit_amount_spec.rb +9 -0
- data/spec/models/equity_spec.rb +9 -0
- data/spec/models/expense_spec.rb +9 -0
- data/spec/models/liability_spec.rb +9 -0
- data/spec/models/revenue_spec.rb +9 -0
- data/spec/models/transaction_spec.rb +118 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/account_shared_examples.rb +57 -0
- data/spec/support/amount_shared_examples.rb +21 -0
- metadata +306 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 50060f8c9f1f6d7255e40b89573a8f8b57557b0a
|
4
|
+
data.tar.gz: baf5ccaa16ad6488d6a94997692e780932bdd35e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6c6c898e8105dfa1bf731b6985108e0110413d6cad12d4dd2cfdb8b0710e4ce585a2f7a88a15d2c290b6f69d0118d9e20782035fb37168596a221d8ef2120f2f
|
7
|
+
data.tar.gz: 6543bd307114e95356464413db9633a0de666576c19a14541a7eb3f84d4719052bc09b222a6431caca23406ac860e58a026e5a0b9dc921d7efda57c1d9808cd6
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2013-2014 Lenno Nagel, 2010 Michael Bulat
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
Except as contained in this notice, the name(s) of the above copyright holders
|
14
|
+
shall not be used in advertising or otherwise to promote the sale, use or
|
15
|
+
other dealings in this Software without prior written authorization.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23
|
+
THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Event-Sourced Accounting
|
2
|
+
=================
|
3
|
+
|
4
|
+
The Event-Sourced Accounting plugin provides an event-sourced double entry accounting system.
|
5
|
+
It uses the data models of a Rails application as a data source and automatically
|
6
|
+
generates accounting transactions based on defined accounting rules.
|
7
|
+
|
8
|
+
This plugin began life as a fork of the [Plutus](https://github.com/mbulat/plutus) plugin with
|
9
|
+
many added features and refactored compontents. As the aims of the ESA plug-in have completely
|
10
|
+
changed compared to the original project, it warrants a release under its own name.
|
11
|
+
|
12
|
+
The API is not yet declared frozen and may change, as some refactoring is still due.
|
13
|
+
The documentation and test coverage is expected to be completed within April-May 2014.
|
14
|
+
|
15
|
+
|
16
|
+
Installation
|
17
|
+
============
|
18
|
+
|
19
|
+
- Add `gem "event-sourced-accounting"` to your Gemfile
|
20
|
+
|
21
|
+
- generate migration files with `rails g esa`
|
22
|
+
|
23
|
+
- run migrations `rake db:migrate`
|
24
|
+
|
25
|
+
- add `include ESA::Traits::Accountable` to relevant models
|
26
|
+
|
27
|
+
- implement the corresponding Event, Flag, Ruleset and Transaction classes for relevant models
|
28
|
+
|
29
|
+
|
30
|
+
Development
|
31
|
+
============
|
32
|
+
|
33
|
+
Any comments and contributions are welcome. Will gladly accept patches sent via pull requests.
|
34
|
+
|
35
|
+
- run rspec tests simply with `rake`
|
36
|
+
|
37
|
+
- update documentation with `yard`
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
@@ -0,0 +1,86 @@
|
|
1
|
+
.esa_container {
|
2
|
+
|
3
|
+
/* CSS from http://wptheming.com/2011/01/simple-table-css3/ */
|
4
|
+
|
5
|
+
margin:0;
|
6
|
+
padding:0;
|
7
|
+
border:0;
|
8
|
+
font-family: "Helvetica Neue",Arial, Helvetica, sans-serif;
|
9
|
+
|
10
|
+
/*
|
11
|
+
Pretty Table Styling
|
12
|
+
CSS Tricks also has a nice writeup: http://css-tricks.com/feature-table-design/
|
13
|
+
*/
|
14
|
+
|
15
|
+
table {
|
16
|
+
overflow:hidden;
|
17
|
+
border:1px solid #d3d3d3;
|
18
|
+
background:#fefefe;
|
19
|
+
width:70%;
|
20
|
+
margin:5% auto 0;
|
21
|
+
-moz-border-radius:5px; /* FF1+ */
|
22
|
+
-webkit-border-radius:5px; /* Saf3-4 */
|
23
|
+
border-radius:5px;
|
24
|
+
-moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
|
25
|
+
-webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
|
26
|
+
}
|
27
|
+
|
28
|
+
th, td {padding:10px 18px 10px; }
|
29
|
+
|
30
|
+
th {padding-top:22px; text-shadow: 1px 1px 1px #fff; background:#e8eaeb;}
|
31
|
+
|
32
|
+
td {border-top:1px solid #e0e0e0; border-right:1px solid #e0e0e0;}
|
33
|
+
|
34
|
+
tr.even td {background:#f6f6f6;}
|
35
|
+
|
36
|
+
td.first, th.first {text-align:left}
|
37
|
+
|
38
|
+
td.last {border-right:none;}
|
39
|
+
|
40
|
+
/*
|
41
|
+
Background gradients are completely unnessary but a neat effect.
|
42
|
+
*/
|
43
|
+
|
44
|
+
td {
|
45
|
+
background: -moz-linear-gradient(100% 25% 90deg, #fefefe, #f9f9f9);
|
46
|
+
background: -webkit-gradient(linear, 0% 0%, 0% 25%, from(#f9f9f9), to(#fefefe));
|
47
|
+
}
|
48
|
+
|
49
|
+
tr.even td {
|
50
|
+
background: -moz-linear-gradient(100% 25% 90deg, #f6f6f6, #f1f1f1);
|
51
|
+
background: -webkit-gradient(linear, 0% 0%, 0% 25%, from(#f1f1f1), to(#f6f6f6));
|
52
|
+
}
|
53
|
+
|
54
|
+
th {
|
55
|
+
background: -moz-linear-gradient(100% 20% 90deg, #e8eaeb, #ededed);
|
56
|
+
background: -webkit-gradient(linear, 0% 0%, 0% 20%, from(#ededed), to(#e8eaeb));
|
57
|
+
}
|
58
|
+
|
59
|
+
/*
|
60
|
+
I know this is annoying, but we need dditional styling so webkit will recognize rounded corners on background elements.
|
61
|
+
Nice write up of this issue: http://www.onenaught.com/posts/266/css-inner-elements-breaking-border-radius
|
62
|
+
|
63
|
+
And, since we've applied the background colors to td/th element because of IE, Gecko browsers also need it.
|
64
|
+
*/
|
65
|
+
|
66
|
+
tr:first-child th.first {
|
67
|
+
-moz-border-radius-topleft:5px;
|
68
|
+
-webkit-border-top-left-radius:5px; /* Saf3-4 */
|
69
|
+
}
|
70
|
+
|
71
|
+
tr:first-child th.last {
|
72
|
+
-moz-border-radius-topright:5px;
|
73
|
+
-webkit-border-top-right-radius:5px; /* Saf3-4 */
|
74
|
+
}
|
75
|
+
|
76
|
+
tr:last-child td.first {
|
77
|
+
-moz-border-radius-bottomleft:5px;
|
78
|
+
-webkit-border-bottom-left-radius:5px; /* Saf3-4 */
|
79
|
+
}
|
80
|
+
|
81
|
+
tr:last-child td.last {
|
82
|
+
-moz-border-radius-bottomright:5px;
|
83
|
+
-webkit-border-bottom-right-radius:5px; /* Saf3-4 */
|
84
|
+
}
|
85
|
+
|
86
|
+
}
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module ESA
|
2
|
+
# The Account class represents accounts in the system. Each account must be subclassed as one of the following types:
|
3
|
+
#
|
4
|
+
# TYPE | NORMAL BALANCE | DESCRIPTION
|
5
|
+
# --------------------------------------------------------------------------
|
6
|
+
# Asset | Debit | Resources owned by the Business Entity
|
7
|
+
# Liability | Credit | Debts owed to outsiders
|
8
|
+
# Equity | Credit | Owners rights to the Assets
|
9
|
+
# Revenue | Credit | Increases in owners equity
|
10
|
+
# Expense | Debit | Assets or services consumed in the generation of revenue
|
11
|
+
#
|
12
|
+
# Each account can also be marked as a "Contra Account". A contra account will have it's
|
13
|
+
# normal balance swapped. For example, to remove equity, a "Drawing" account may be created
|
14
|
+
# as a contra equity account as follows:
|
15
|
+
#
|
16
|
+
# ESA::Equity.create(:name => "Drawing", contra => true)
|
17
|
+
#
|
18
|
+
# @author Lenno Nagel, Michael Bulat
|
19
|
+
class Account < ActiveRecord::Base
|
20
|
+
extend ::Enumerize
|
21
|
+
|
22
|
+
attr_accessible :chart, :type, :code, :name, :contra
|
23
|
+
attr_accessible :chart, :type, :code, :name, :contra, :as => :admin
|
24
|
+
attr_readonly :chart
|
25
|
+
|
26
|
+
belongs_to :chart
|
27
|
+
has_many :amounts, :extend => Associations::AmountsExtension
|
28
|
+
has_many :transactions, :through => :amounts, :source => :transaction
|
29
|
+
|
30
|
+
enumerize :normal_balance, in: [:none, :debit, :credit]
|
31
|
+
|
32
|
+
after_initialize :default_values
|
33
|
+
|
34
|
+
before_validation :update_normal_balance
|
35
|
+
validates_presence_of :type, :name, :chart, :normal_balance
|
36
|
+
validates_uniqueness_of :code, :scope => :chart_id
|
37
|
+
validates_uniqueness_of :name, :scope => :chart_id
|
38
|
+
|
39
|
+
# The balance of the account.
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# >> account.balance
|
43
|
+
# => #<BigDecimal:103259bb8,'0.2E4',4(12)>
|
44
|
+
#
|
45
|
+
# @return [BigDecimal] The decimal value balance
|
46
|
+
def balance
|
47
|
+
if self.normal_balance.debit?
|
48
|
+
self.amounts.balance
|
49
|
+
elsif self.normal_balance.credit?
|
50
|
+
- self.amounts.balance
|
51
|
+
else
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.valid_type?(type)
|
57
|
+
type.in? ["Asset", "Liability", "Equity", "Revenue", "Expense"]
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.namespaced_type(type)
|
61
|
+
if valid_type?(type)
|
62
|
+
"ESA::Accounts::#{type}"
|
63
|
+
else
|
64
|
+
type
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def default_values
|
71
|
+
self.chart ||= Chart.where(:name => 'Chart of Accounts').first_or_create if self.chart_id.nil?
|
72
|
+
self.normal_balance ||= :none
|
73
|
+
end
|
74
|
+
|
75
|
+
# The normal balance for the account. Must be overridden in implementations.
|
76
|
+
def update_normal_balance
|
77
|
+
self.normal_balance = :none
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ESA
|
2
|
+
module Accounts
|
3
|
+
# The Asset class is an account type used to represents resources owned by the business entity.
|
4
|
+
#
|
5
|
+
# === Normal Balance
|
6
|
+
# The normal balance on Asset accounts is a *Debit*.
|
7
|
+
#
|
8
|
+
# @see http://en.wikipedia.org/wiki/Asset Assets
|
9
|
+
#
|
10
|
+
# @author Lenno Nagel
|
11
|
+
class Asset < ESA::Account
|
12
|
+
# The normal balance for the account. Must be overridden in implementations.
|
13
|
+
def update_normal_balance
|
14
|
+
unless self.contra
|
15
|
+
self.normal_balance = :debit
|
16
|
+
else
|
17
|
+
self.normal_balance = :credit
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ESA
|
2
|
+
module Accounts
|
3
|
+
# The Equity class is an account type used to represents owners rights to the assets.
|
4
|
+
#
|
5
|
+
# === Normal Balance
|
6
|
+
# The normal balance on Equity accounts is a *Credit*.
|
7
|
+
#
|
8
|
+
# @see http://en.wikipedia.org/wiki/Equity_(finance) Equity
|
9
|
+
#
|
10
|
+
# @author Lenno Nagel
|
11
|
+
class Equity < ESA::Account
|
12
|
+
# The normal balance for the account. Must be overridden in implementations.
|
13
|
+
def update_normal_balance
|
14
|
+
unless self.contra
|
15
|
+
self.normal_balance = :credit
|
16
|
+
else
|
17
|
+
self.normal_balance = :debit
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ESA
|
2
|
+
module Accounts
|
3
|
+
# The Expense class is an account type used to represents assets or services consumed in the generation of revenue.
|
4
|
+
#
|
5
|
+
# === Normal Balance
|
6
|
+
# The normal balance on Expense accounts is a *Debit*.
|
7
|
+
#
|
8
|
+
# @see http://en.wikipedia.org/wiki/Expense Expenses
|
9
|
+
#
|
10
|
+
# @author Lenno Nagel
|
11
|
+
class Expense < ESA::Account
|
12
|
+
# The normal balance for the account. Must be overridden in implementations.
|
13
|
+
def update_normal_balance
|
14
|
+
unless self.contra
|
15
|
+
self.normal_balance = :debit
|
16
|
+
else
|
17
|
+
self.normal_balance = :credit
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ESA
|
2
|
+
module Accounts
|
3
|
+
# The Liability class is an account type used to represents debts owed to outsiders.
|
4
|
+
#
|
5
|
+
# === Normal Balance
|
6
|
+
# The normal balance on Liability accounts is a *Credit*.
|
7
|
+
#
|
8
|
+
# @see http://en.wikipedia.org/wiki/Liability_(financial_accounting) Liability
|
9
|
+
#
|
10
|
+
# @author Lenno Nagel
|
11
|
+
class Liability < ESA::Account
|
12
|
+
# The normal balance for the account. Must be overridden in implementations.
|
13
|
+
def update_normal_balance
|
14
|
+
unless self.contra
|
15
|
+
self.normal_balance = :credit
|
16
|
+
else
|
17
|
+
self.normal_balance = :debit
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ESA
|
2
|
+
module Accounts
|
3
|
+
# The Revenue class is an account type used to represents increases in owners equity.
|
4
|
+
#
|
5
|
+
# === Normal Balance
|
6
|
+
# The normal balance on Revenue accounts is a *Credit*.
|
7
|
+
#
|
8
|
+
# @see http://en.wikipedia.org/wiki/Revenue Revenue
|
9
|
+
#
|
10
|
+
# @author Lenno Nagel
|
11
|
+
class Revenue < ESA::Account
|
12
|
+
# The normal balance for the account. Must be overridden in implementations.
|
13
|
+
def update_normal_balance
|
14
|
+
unless self.contra
|
15
|
+
self.normal_balance = :credit
|
16
|
+
else
|
17
|
+
self.normal_balance = :debit
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ESA
|
2
|
+
# The Amount class represents debit and credit amounts in the system.
|
3
|
+
#
|
4
|
+
# @abstract
|
5
|
+
# An amount must be a subclass as either a debit or a credit to be saved to the database.
|
6
|
+
#
|
7
|
+
# @author Lenno Nagel, Michael Bulat
|
8
|
+
class Amount < ActiveRecord::Base
|
9
|
+
attr_accessible :type, :account, :amount, :transaction
|
10
|
+
|
11
|
+
belongs_to :transaction
|
12
|
+
belongs_to :account
|
13
|
+
|
14
|
+
validates_presence_of :type, :amount, :transaction, :account
|
15
|
+
|
16
|
+
scope :credits, lambda { where(type: Amounts::Credit) }
|
17
|
+
scope :debits, lambda { where(type: Amounts::Debit) }
|
18
|
+
|
19
|
+
def is_credit?
|
20
|
+
self.is_a? Amounts::Credit
|
21
|
+
end
|
22
|
+
|
23
|
+
def is_debit?
|
24
|
+
self.is_a? Amounts::Debit
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ESA
|
2
|
+
module Amounts
|
3
|
+
# The Credit class represents credit entries in the transaction journal.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# credit_amount = ESA::Amounts::Credit.new(:account => revenue, :amount => 1000)
|
7
|
+
#
|
8
|
+
# @author Lenno Nagel
|
9
|
+
class Credit < ESA::Amount
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ESA
|
2
|
+
module Amounts
|
3
|
+
# The Debit class represents debit entries in the transaction journal.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# debit_amount = ESA::Amounts::Debit.new(:account => cash, :amount => 1000)
|
7
|
+
#
|
8
|
+
# @author Lenno Nagel
|
9
|
+
class Debit < ESA::Amount
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module ESA
|
2
|
+
module Associations
|
3
|
+
# Association extension for has_many :amounts relations. Internal.
|
4
|
+
module AmountsExtension
|
5
|
+
# Returns a total sum of the referenced Amount objects
|
6
|
+
def total
|
7
|
+
sums = group('esa_amounts.amount is null').
|
8
|
+
sum(:amount)
|
9
|
+
|
10
|
+
checked = sums.map do |s|
|
11
|
+
amount_is_nil, amount = s.flatten
|
12
|
+
amount
|
13
|
+
end
|
14
|
+
|
15
|
+
if checked.all?
|
16
|
+
return checked.inject(BigDecimal(0)){|x,y| x + y}
|
17
|
+
else
|
18
|
+
return nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a sum of the referenced Amount objects.
|
23
|
+
def balance
|
24
|
+
sums = group('esa_amounts.amount is null').
|
25
|
+
group('esa_amounts.type').
|
26
|
+
sum(:amount)
|
27
|
+
|
28
|
+
signed = sums.map do |s|
|
29
|
+
amount_is_nil, amount_type, amount = s.flatten
|
30
|
+
amount_type = amount_type.demodulize.downcase
|
31
|
+
|
32
|
+
if amount_type == "debit"
|
33
|
+
amount
|
34
|
+
elsif amount_type == "credit"
|
35
|
+
- amount
|
36
|
+
else
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if signed.all?
|
42
|
+
return signed.inject(BigDecimal(0)){|x,y| x + y}
|
43
|
+
else
|
44
|
+
return nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns a sum of the referenced Amount objects.
|
49
|
+
def iterated_total
|
50
|
+
amounts = map(&:amount)
|
51
|
+
|
52
|
+
if amounts.all?
|
53
|
+
amounts.inject(BigDecimal(0)){|x,y| x + y}
|
54
|
+
else
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns a sum of the referenced Amount objects.
|
60
|
+
def iterated_balance
|
61
|
+
amounts = map do |a|
|
62
|
+
if a.is_debit?
|
63
|
+
a.amount
|
64
|
+
elsif a.is_credit? and not a.amount.nil?
|
65
|
+
- a.amount
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if amounts.all?
|
72
|
+
amounts.inject(BigDecimal(0)){|x,y| x + y}
|
73
|
+
else
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ESA
|
2
|
+
module Associations
|
3
|
+
module EventsExtension
|
4
|
+
# This function adds an event only if there were no previous events,
|
5
|
+
# or the previous event was a different one, avoiding duplicates.
|
6
|
+
def maybe(attrs)
|
7
|
+
events = proxy_association.owner.esa_events
|
8
|
+
last_event = events.order('time DESC, created_at DESC').first
|
9
|
+
|
10
|
+
# make sure that the previous event was not of the same event type
|
11
|
+
if last_event.nil? or last_event.nature != attrs[:nature]
|
12
|
+
if attrs[:time].present? and last_event.present? and last_event.time.present? and last_event.time > attrs[:time]
|
13
|
+
# we cannot input past events, so let's make it most recent
|
14
|
+
attrs[:time] = last_event.time
|
15
|
+
e = events.new(attrs)
|
16
|
+
else
|
17
|
+
e = events.new(attrs)
|
18
|
+
end
|
19
|
+
e.save
|
20
|
+
else
|
21
|
+
# we didn't need to add anything
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def hashes
|
27
|
+
proxy_association.owner.esa_events.
|
28
|
+
map{|e| {:nature => e.nature, :time => e.time}}.
|
29
|
+
sort_by{|e| e[:time]}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ESA
|
2
|
+
module Associations
|
3
|
+
module FlagsExtension
|
4
|
+
def most_recent(nature, time=Time.zone.now, exclude=nil)
|
5
|
+
query = where(nature: nature).
|
6
|
+
where('time <= ?', time)
|
7
|
+
|
8
|
+
if exclude.present?
|
9
|
+
query = query.where('esa_flags.id not in (?)', exclude)
|
10
|
+
end
|
11
|
+
|
12
|
+
query.order('time DESC, created_at DESC').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def is_set?(nature, time=Time.zone.now, exclude=nil)
|
16
|
+
most_recent = most_recent(nature, time, exclude)
|
17
|
+
if most_recent.present?
|
18
|
+
most_recent.is_set? # return the set bit of this flag
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def transition_for(flag)
|
25
|
+
if flag.state and not is_set?(flag.nature, flag.time, flag.id)
|
26
|
+
1
|
27
|
+
elsif not flag.state and is_set?(flag.nature, flag.time, flag.id)
|
28
|
+
-1
|
29
|
+
else
|
30
|
+
0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ESA
|
2
|
+
# The Chart class represents an organized set of accounts in the system.
|
3
|
+
#
|
4
|
+
# @author Lenno Nagel
|
5
|
+
class Chart < ActiveRecord::Base
|
6
|
+
include Traits::Extendable
|
7
|
+
|
8
|
+
attr_accessible :name
|
9
|
+
|
10
|
+
has_many :accounts
|
11
|
+
has_many :rulesets
|
12
|
+
|
13
|
+
has_many :events, :through => :rulesets, :uniq => true
|
14
|
+
has_many :flags, :through => :rulesets, :uniq => true
|
15
|
+
has_many :transactions, :through => :accounts, :uniq => true
|
16
|
+
has_many :amounts, :through => :accounts, :uniq => true, :extend => Associations::AmountsExtension
|
17
|
+
|
18
|
+
after_initialize :default_values
|
19
|
+
|
20
|
+
validates_presence_of :name
|
21
|
+
validates_uniqueness_of :name
|
22
|
+
|
23
|
+
# The trial balance of all accounts in the system. This should always equal zero,
|
24
|
+
# otherwise there is an error in the system.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# >> chart.trial_balance.to_i
|
28
|
+
# => 0
|
29
|
+
#
|
30
|
+
# @return [BigDecimal] The decimal value balance of all accounts
|
31
|
+
def trial_balance
|
32
|
+
self.amounts.balance
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def default_values
|
38
|
+
self.name ||= "Chart of Accounts"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|