event_sourced_accounting 0.1.0
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.
- 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
|