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
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
# The Context provides a persisted filtered view on the objects related to a Chart.
|
|
3
|
+
#
|
|
4
|
+
# @author Lenno Nagel
|
|
5
|
+
class Context < ActiveRecord::Base
|
|
6
|
+
attr_accessible :chart, :chart_id, :parent, :parent_id, :type, :name, :namespace, :position
|
|
7
|
+
attr_accessible :chart, :chart_id, :parent, :parent_id, :type, :name, :namespace, :position, :start_date, :end_date, :as => :admin
|
|
8
|
+
attr_readonly :chart, :parent
|
|
9
|
+
|
|
10
|
+
belongs_to :chart
|
|
11
|
+
has_many :accounts, :through => :chart
|
|
12
|
+
has_many :rulesets, :through => :chart
|
|
13
|
+
|
|
14
|
+
has_many :unscoped_events, :through => :rulesets, :source => :events, :uniq => true
|
|
15
|
+
has_many :unscoped_flags, :through => :rulesets, :source => :flags, :uniq => true
|
|
16
|
+
has_many :unscoped_transactions, :through => :accounts, :source => :transactions, :uniq => true
|
|
17
|
+
has_many :unscoped_amounts, :through => :accounts, :source => :amounts, :uniq => true, :extend => Associations::AmountsExtension
|
|
18
|
+
|
|
19
|
+
belongs_to :parent, :class_name => "Context"
|
|
20
|
+
has_many :subcontexts, :class_name => "Context", :foreign_key => "parent_id", :dependent => :destroy
|
|
21
|
+
|
|
22
|
+
after_initialize :default_values, :initialize_filters
|
|
23
|
+
before_validation :update_name, :update_position
|
|
24
|
+
validates_presence_of :chart, :name
|
|
25
|
+
validate :validate_parent
|
|
26
|
+
before_save :enforce_persistence_rule
|
|
27
|
+
|
|
28
|
+
scope :roots, lambda { where(parent_id: nil) }
|
|
29
|
+
scope :subs, lambda { where("esa_contexts.parent_id is not null") }
|
|
30
|
+
|
|
31
|
+
def is_root?
|
|
32
|
+
self.parent_id.nil?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def is_subcontext?
|
|
36
|
+
self.parent_id.present?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def events
|
|
40
|
+
self.apply(self.unscoped_events)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def flags
|
|
44
|
+
self.apply(self.unscoped_flags)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def transactions
|
|
48
|
+
self.apply(self.unscoped_transactions)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def amounts
|
|
52
|
+
self.apply(self.unscoped_amounts)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def apply(relation)
|
|
56
|
+
self.effective_contexts.inject(relation) do |r,context|
|
|
57
|
+
context.inject_filters(r)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def last_transaction_time
|
|
62
|
+
if defined? @last_transaction_time
|
|
63
|
+
@last_transaction_time
|
|
64
|
+
else
|
|
65
|
+
@last_transaction_time = self.transactions.maximum(:created_at)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def check_freshness(depth=0)
|
|
70
|
+
if self.is_update_needed?
|
|
71
|
+
self.update!
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if depth > 0 and self.last_transaction_time.present?
|
|
75
|
+
self.subcontexts.each do |sub|
|
|
76
|
+
if sub.freshness.nil? or sub.freshness <= self.last_transaction_time
|
|
77
|
+
sub.check_freshness(depth - 1)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def is_update_needed?
|
|
84
|
+
if self.freshness.present?
|
|
85
|
+
if self.last_transaction_time.present?
|
|
86
|
+
self.freshness <= self.last_transaction_time
|
|
87
|
+
else
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
true
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def update!
|
|
96
|
+
self.freshness = Time.zone.now
|
|
97
|
+
|
|
98
|
+
Config.context_checkers.each do |checker|
|
|
99
|
+
if checker.respond_to? :check
|
|
100
|
+
checker.check(self)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
self.update_name
|
|
105
|
+
self.save if self.can_be_persisted?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def subcontext_namespaces
|
|
109
|
+
self.subcontexts.pluck(:namespace).compact.uniq
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def effective_contexts
|
|
113
|
+
self.parents_and_self
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def effective_path
|
|
117
|
+
self.effective_contexts.map{|ctx| ctx.namespace || ""}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def effective_start_date
|
|
121
|
+
self.effective_contexts.map(&:start_date).compact.max
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def effective_end_date
|
|
125
|
+
self.effective_contexts.map(&:end_date).compact.min
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def opening_context
|
|
129
|
+
if self.effective_start_date.present?
|
|
130
|
+
end_date = self.effective_start_date - 1.day
|
|
131
|
+
Contexts::OpenCloseContext.new(chart: self.chart, parent: self, end_date: end_date, namespace: 'opening')
|
|
132
|
+
else
|
|
133
|
+
Contexts::EmptyContext.new(chart: self.chart, parent: self, namespace: 'opening')
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def closing_context
|
|
138
|
+
if self.effective_end_date.present?
|
|
139
|
+
Contexts::OpenCloseContext.new(chart: self.chart, parent: self, end_date: self.effective_end_date, namespace: 'closing')
|
|
140
|
+
else
|
|
141
|
+
Contexts::OpenCloseContext.new(chart: self.chart, parent: self, namespace: 'closing')
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def change_total
|
|
146
|
+
if self.debits_total.present? and self.credits_total.present?
|
|
147
|
+
self.debits_total - self.credits_total
|
|
148
|
+
else
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def can_be_persisted?
|
|
154
|
+
self.type.present?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
protected
|
|
158
|
+
|
|
159
|
+
def validate_parent
|
|
160
|
+
if self.parent == self
|
|
161
|
+
errors[:parent] = "cannot self-reference, that would create a loop"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def default_values
|
|
166
|
+
self.chart ||= self.parent.chart if self.chart_id.nil? and not self.parent_id.nil?
|
|
167
|
+
self.namespace ||= (self.type || self.class.name).demodulize.underscore.gsub(/_context$/, '')
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def update_name
|
|
171
|
+
self.name = self.create_name
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def create_name
|
|
175
|
+
if self.type.nil?
|
|
176
|
+
self.chart.name unless self.chart.nil?
|
|
177
|
+
else
|
|
178
|
+
"#{self.type.demodulize} \##{self.id}"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def update_position
|
|
183
|
+
self.position = self.create_position
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def create_position
|
|
187
|
+
nil
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def initialize_filters
|
|
191
|
+
@filters = []
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def inject_filters(relation)
|
|
195
|
+
@filters.select{|f| f.is_a? Proc}.
|
|
196
|
+
inject(relation) do |r,filter|
|
|
197
|
+
filter.call(r)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def parents_and_self
|
|
202
|
+
contexts = [self]
|
|
203
|
+
while contexts.last.parent_id.present? and
|
|
204
|
+
not contexts.last.parent_id.in? contexts.map(&:id) and
|
|
205
|
+
contexts.count < 16 do
|
|
206
|
+
# found a valid parent
|
|
207
|
+
contexts << contexts.last.parent
|
|
208
|
+
end
|
|
209
|
+
contexts.reverse
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def enforce_persistence_rule
|
|
213
|
+
if not self.can_be_persisted?
|
|
214
|
+
raise "#{self.class.name} objects are not intended to be persisted"
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
class ContextProvider
|
|
3
|
+
def self.provided_types
|
|
4
|
+
[]
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.check_subcontexts(context, namespace, options = {})
|
|
8
|
+
existing = existing_subcontexts(context, namespace)
|
|
9
|
+
contained = contained_subcontexts(context, namespace, existing, options)
|
|
10
|
+
|
|
11
|
+
created = contained - existing
|
|
12
|
+
created.each do |sub|
|
|
13
|
+
sub.save if sub.new_record? or sub.changed?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
unregistered = contained - context.subcontexts
|
|
17
|
+
context.subcontexts += unregistered
|
|
18
|
+
|
|
19
|
+
removed = existing - contained
|
|
20
|
+
context.subcontexts -= removed
|
|
21
|
+
|
|
22
|
+
removed.each(&:destroy) if context.can_be_persisted?
|
|
23
|
+
|
|
24
|
+
contained
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.context_id(context, options = {})
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.contained_ids(context, options = {})
|
|
32
|
+
[]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.existing_subcontexts(context, namespace, options = {})
|
|
36
|
+
context.subcontexts.where(type: provided_types, namespace: namespace).all
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.contained_subcontexts(context, namespace, existing, options = {})
|
|
40
|
+
contained_ids = contained_ids(context, options)
|
|
41
|
+
existing_ids = existing.map{|sub| context_id(sub, options)}
|
|
42
|
+
|
|
43
|
+
new_ids = contained_ids - existing_ids
|
|
44
|
+
|
|
45
|
+
new_subcontexts = new_ids.map do |id|
|
|
46
|
+
instantiate(context, namespace, id, options)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
new_subcontexts + existing.select{|sub| context_id(sub).in? contained_ids}
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module ContextProviders
|
|
3
|
+
class AccountContextProvider < ESA::ContextProvider
|
|
4
|
+
def self.provided_types
|
|
5
|
+
["ESA::Contexts::AccountContext"]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.context_id(context, options = {})
|
|
9
|
+
context.account_id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.contained_ids(context, options = {})
|
|
13
|
+
context.amounts.pluck(:account_id).uniq
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.instantiate(parent, namespace, id, options = {})
|
|
17
|
+
ESA::Contexts::AccountContext.new(chart_id: parent.chart_id, parent_id: parent.id, namespace: namespace, account_id: id)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module ContextProviders
|
|
3
|
+
class AccountableContextProvider < ESA::ContextProvider
|
|
4
|
+
def self.provided_types
|
|
5
|
+
["ESA::Contexts::AccountableContext"]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.context_id(context, options = {})
|
|
9
|
+
[context.accountable_id, context.accountable_type]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.contained_ids(context, options = {})
|
|
13
|
+
context.transactions.pluck([:accountable_id, :accountable_type]).uniq
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.instantiate(parent, namespace, id, options = {})
|
|
17
|
+
accountable_id, accountable_type = id
|
|
18
|
+
ESA::Contexts::AccountableContext.new(chart_id: parent.chart_id, parent_id: parent.id, namespace: namespace, accountable_id: accountable_id, accountable_type: accountable_type)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module ContextProviders
|
|
3
|
+
class AccountableTypeContextProvider < ESA::ContextProvider
|
|
4
|
+
def self.provided_types
|
|
5
|
+
["ESA::Contexts::AccountableTypeContext"]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.context_id(context, options = {})
|
|
9
|
+
context.accountable_type
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.contained_ids(context, options = {})
|
|
13
|
+
context.transactions.pluck(:accountable_type).uniq
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.instantiate(parent, namespace, id, options = {})
|
|
17
|
+
ESA::Contexts::AccountableTypeContext.new(chart_id: parent.chart_id, parent_id: parent.id, namespace: namespace, accountable_type: id)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module ContextProviders
|
|
3
|
+
class DateContextProvider < ESA::ContextProvider
|
|
4
|
+
def self.provided_types
|
|
5
|
+
["ESA::Contexts::DateContext"]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.context_id(context, options = {})
|
|
9
|
+
[context.start_date, context.end_date]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.contained_ids(context, options = {})
|
|
13
|
+
dates = context.transactions.pluck("date(esa_transactions.time)").uniq.sort
|
|
14
|
+
|
|
15
|
+
if options[:period].present? and options[:period] == :month
|
|
16
|
+
dates.group_by{|d| [d.year, d.month]}.keys.
|
|
17
|
+
map do |year,month|
|
|
18
|
+
start_date = Date.new(year, month, 1)
|
|
19
|
+
end_date = start_date.end_of_month
|
|
20
|
+
[start_date, end_date]
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
dates.zip dates
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.instantiate(parent, namespace, id, options = {})
|
|
28
|
+
start_date, end_date = id
|
|
29
|
+
ESA::Contexts::DateContext.new(chart_id: parent.chart_id, parent_id: parent.id, namespace: namespace, start_date: start_date, end_date: end_date)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module Contexts
|
|
3
|
+
class AccountContext < ESA::Context
|
|
4
|
+
attr_accessible :account, :account_id
|
|
5
|
+
attr_readonly :account, :account_id
|
|
6
|
+
|
|
7
|
+
belongs_to :account
|
|
8
|
+
|
|
9
|
+
validates_presence_of :account
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
|
|
13
|
+
def create_name
|
|
14
|
+
self.account.name unless self.account.nil?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create_position
|
|
18
|
+
self.account.code.gsub(/[^0-9]/, '').to_i unless self.account.nil? or self.account.code.nil?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize_filters
|
|
22
|
+
@filters = [lambda { |relation| relation.with_account(self.account_id) }]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module Contexts
|
|
3
|
+
class AccountableContext < ESA::Context
|
|
4
|
+
attr_accessible :accountable, :accountable_id, :accountable_type
|
|
5
|
+
attr_readonly :accountable, :accountable_id, :accountable_type
|
|
6
|
+
|
|
7
|
+
belongs_to :accountable, :polymorphic => true
|
|
8
|
+
|
|
9
|
+
validates_presence_of :accountable
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
|
|
13
|
+
def create_name
|
|
14
|
+
"#{self.accountable_type} #{self.accountable_id}" unless self.accountable_type.nil?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize_filters
|
|
18
|
+
@filters = []
|
|
19
|
+
|
|
20
|
+
if self.accountable_id.present? and self.accountable_type.present?
|
|
21
|
+
@filters << lambda { |relation| relation.with_accountable(self.accountable_id, self.accountable_type) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module Contexts
|
|
3
|
+
class AccountableTypeContext < ESA::Context
|
|
4
|
+
attr_accessible :accountable_type
|
|
5
|
+
attr_readonly :accountable_type
|
|
6
|
+
|
|
7
|
+
validates_presence_of :accountable_type
|
|
8
|
+
|
|
9
|
+
protected
|
|
10
|
+
|
|
11
|
+
def create_name
|
|
12
|
+
"#{self.accountable_type} accountables" unless self.accountable_type.nil?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize_filters
|
|
16
|
+
@filters = []
|
|
17
|
+
|
|
18
|
+
if self.accountable_type.present?
|
|
19
|
+
@filters << lambda { |relation| relation.with_accountable_type(self.accountable_type) }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module Contexts
|
|
3
|
+
class CreatedAtContext < ESA::Context
|
|
4
|
+
def created_at
|
|
5
|
+
@created_at
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def created_at=(timespec)
|
|
9
|
+
@created_at = timespec
|
|
10
|
+
@filters = [lambda { |relation| relation.where(created_at: timespec) }]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def effective_path
|
|
14
|
+
if self.parent_id.blank?
|
|
15
|
+
[]
|
|
16
|
+
else
|
|
17
|
+
self.parent.effective_path
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def can_be_persisted?
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module Contexts
|
|
3
|
+
class DateContext < ESA::Context
|
|
4
|
+
attr_accessible :start_date, :end_date
|
|
5
|
+
attr_readonly :start_date, :end_date
|
|
6
|
+
|
|
7
|
+
validate :validate_dates
|
|
8
|
+
|
|
9
|
+
def preceeding_context
|
|
10
|
+
if self.start_date.present?
|
|
11
|
+
DateContext.new chart: self.chart,
|
|
12
|
+
end_date: self.start_date - 1.day
|
|
13
|
+
else
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def following_context
|
|
19
|
+
if self.end_date.present?
|
|
20
|
+
DateContext.new chart: self.chart,
|
|
21
|
+
start_date: self.end_date + 1.day
|
|
22
|
+
else
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def validate_dates
|
|
30
|
+
if self.start_date.nil? and self.end_date.nil?
|
|
31
|
+
errors[:start_date] = "at least one of the two dates must be provided"
|
|
32
|
+
errors[:end_date] = "at least one of the two dates must be provided"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def create_name
|
|
37
|
+
if self.start_date.present? and self.end_date.present?
|
|
38
|
+
if self.start_date == self.end_date
|
|
39
|
+
"#{self.start_date.to_s}"
|
|
40
|
+
else
|
|
41
|
+
"#{self.start_date.to_s} - #{self.end_date.to_s}"
|
|
42
|
+
end
|
|
43
|
+
elsif self.start_date.present?
|
|
44
|
+
"#{self.start_date.to_s} - ..."
|
|
45
|
+
elsif self.end_date.present?
|
|
46
|
+
"... - #{self.end_date.to_s}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def create_position
|
|
51
|
+
if self.start_date.present?
|
|
52
|
+
self.start_date.to_time.to_i
|
|
53
|
+
elsif self.end_date.present?
|
|
54
|
+
self.end_date.to_time.to_i
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def initialize_filters
|
|
59
|
+
@filters = []
|
|
60
|
+
|
|
61
|
+
if self.start_date.present? and self.end_date.present?
|
|
62
|
+
@filters << lambda { |relation| relation.between_dates(self.start_date, self.end_date) }
|
|
63
|
+
elsif self.start_date.present?
|
|
64
|
+
@filters << lambda { |relation| relation.with_date_gte(self.start_date) }
|
|
65
|
+
elsif self.end_date.present?
|
|
66
|
+
@filters << lambda { |relation| relation.with_date_lte(self.end_date) }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module Contexts
|
|
3
|
+
class EmptyContext < ESA::Context
|
|
4
|
+
def effective_contexts
|
|
5
|
+
[self]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
protected
|
|
9
|
+
|
|
10
|
+
def create_name
|
|
11
|
+
"Empty"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize_filters
|
|
15
|
+
@filters = [lambda { |relation| relation.where('1=0') }]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module Contexts
|
|
3
|
+
class OpenCloseContext < DateContext
|
|
4
|
+
def effective_contexts
|
|
5
|
+
self.parents_and_self.reject{|ctx| ctx.type == DateContext.to_s}
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
protected
|
|
9
|
+
|
|
10
|
+
def validate_dates
|
|
11
|
+
# OpenCloseContext can be initialized with no dates
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
# The Event class represents an event of significance to accounting,
|
|
3
|
+
# which triggers the creation of Flags, which in turn create Transactions.
|
|
4
|
+
#
|
|
5
|
+
# @author Lenno Nagel
|
|
6
|
+
class Event < ActiveRecord::Base
|
|
7
|
+
include Traits::Extendable
|
|
8
|
+
extend ::Enumerize
|
|
9
|
+
|
|
10
|
+
attr_accessible :time, :nature, :accountable, :ruleset
|
|
11
|
+
attr_readonly :time, :nature, :accountable, :ruleset
|
|
12
|
+
|
|
13
|
+
belongs_to :accountable, :polymorphic => true
|
|
14
|
+
belongs_to :ruleset
|
|
15
|
+
has_many :flags
|
|
16
|
+
has_many :transactions, :through => :flags
|
|
17
|
+
has_many :amounts, :through => :transactions, :extend => Associations::AmountsExtension
|
|
18
|
+
|
|
19
|
+
enumerize :nature, in: [:unknown, :adjustment]
|
|
20
|
+
|
|
21
|
+
after_initialize :default_values
|
|
22
|
+
validates_presence_of :time, :nature, :accountable, :ruleset
|
|
23
|
+
validates_inclusion_of :processed, :in => [true, false]
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def default_values
|
|
28
|
+
self.time ||= Time.zone.now if self.time.nil?
|
|
29
|
+
self.ruleset ||= Ruleset.extension_instance(self.accountable) if self.ruleset_id.nil? and not self.accountable_id.nil?
|
|
30
|
+
self.processed ||= false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module ESA
|
|
2
|
+
module Filters
|
|
3
|
+
module AccountFilter
|
|
4
|
+
module FlagTransactionAmountAccount
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
scope :with_account, lambda { |account| joins(:flags => {:transactions => :amounts}).where(esa_amounts: {account_id: account}) }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module TransactionAmountAccount
|
|
13
|
+
extend ActiveSupport::Concern
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
scope :with_account, lambda { |account| joins(:transactions => :amounts).where(esa_amounts: {account_id: account}) }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module AmountAccount
|
|
21
|
+
extend ActiveSupport::Concern
|
|
22
|
+
|
|
23
|
+
included do
|
|
24
|
+
scope :with_account, lambda { |account| joins(:amounts).where(esa_amounts: {account_id: account}) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module ObjectAccount
|
|
29
|
+
extend ActiveSupport::Concern
|
|
30
|
+
|
|
31
|
+
included do
|
|
32
|
+
scope :with_account, lambda { |account| where(account_id: account) }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
ESA::Amount.send :include, ESA::Filters::AccountFilter::ObjectAccount
|
|
40
|
+
ESA::Event.send :include, ESA::Filters::AccountFilter::FlagTransactionAmountAccount
|
|
41
|
+
ESA::Flag.send :include, ESA::Filters::AccountFilter::TransactionAmountAccount
|
|
42
|
+
ESA::Transaction.send :include, ESA::Filters::AccountFilter::AmountAccount
|