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
@@ -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
|