event_sourced_accounting 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +23 -0
  3. data/README.markdown +37 -0
  4. data/Rakefile +11 -0
  5. data/app/assets/javascripts/esa/application.js +15 -0
  6. data/app/assets/stylesheets/esa/application.css +13 -0
  7. data/app/assets/stylesheets/esa/main.css.scss +86 -0
  8. data/app/models/esa/account.rb +80 -0
  9. data/app/models/esa/accounts/asset.rb +22 -0
  10. data/app/models/esa/accounts/equity.rb +22 -0
  11. data/app/models/esa/accounts/expense.rb +22 -0
  12. data/app/models/esa/accounts/liability.rb +22 -0
  13. data/app/models/esa/accounts/revenue.rb +22 -0
  14. data/app/models/esa/amount.rb +27 -0
  15. data/app/models/esa/amounts/credit.rb +12 -0
  16. data/app/models/esa/amounts/debit.rb +12 -0
  17. data/app/models/esa/associations/amounts_extension.rb +79 -0
  18. data/app/models/esa/associations/events_extension.rb +33 -0
  19. data/app/models/esa/associations/flags_extension.rb +35 -0
  20. data/app/models/esa/associations/transactions_extension.rb +7 -0
  21. data/app/models/esa/chart.rb +41 -0
  22. data/app/models/esa/context.rb +218 -0
  23. data/app/models/esa/context_provider.rb +52 -0
  24. data/app/models/esa/context_providers/account_context_provider.rb +21 -0
  25. data/app/models/esa/context_providers/accountable_context_provider.rb +22 -0
  26. data/app/models/esa/context_providers/accountable_type_context_provider.rb +21 -0
  27. data/app/models/esa/context_providers/date_context_provider.rb +33 -0
  28. data/app/models/esa/contexts/account_context.rb +26 -0
  29. data/app/models/esa/contexts/accountable_context.rb +26 -0
  30. data/app/models/esa/contexts/accountable_type_context.rb +24 -0
  31. data/app/models/esa/contexts/created_at_context.rb +26 -0
  32. data/app/models/esa/contexts/date_context.rb +71 -0
  33. data/app/models/esa/contexts/empty_context.rb +19 -0
  34. data/app/models/esa/contexts/filter_context.rb +11 -0
  35. data/app/models/esa/contexts/open_close_context.rb +15 -0
  36. data/app/models/esa/event.rb +33 -0
  37. data/app/models/esa/filters/account_filter.rb +42 -0
  38. data/app/models/esa/filters/accountable_filter.rb +58 -0
  39. data/app/models/esa/filters/accountable_type_filter.rb +26 -0
  40. data/app/models/esa/filters/chart_filter.rb +17 -0
  41. data/app/models/esa/filters/context_filter.rb +35 -0
  42. data/app/models/esa/filters/date_time_filter.rb +52 -0
  43. data/app/models/esa/flag.rb +70 -0
  44. data/app/models/esa/ruleset.rb +175 -0
  45. data/app/models/esa/traits/accountable.rb +21 -0
  46. data/app/models/esa/traits/extendable.rb +93 -0
  47. data/app/models/esa/traits/or_scope.rb +35 -0
  48. data/app/models/esa/traits/union_scope.rb +24 -0
  49. data/app/models/esa/transaction.rb +74 -0
  50. data/config/backtrace_silencers.rb +7 -0
  51. data/config/database.yml +5 -0
  52. data/config/inflections.rb +10 -0
  53. data/config/mime_types.rb +5 -0
  54. data/config/routes.rb +6 -0
  55. data/config/secret_token.rb +7 -0
  56. data/config/session_store.rb +8 -0
  57. data/lib/esa/balance_checker.rb +17 -0
  58. data/lib/esa/blocking_processor.rb +158 -0
  59. data/lib/esa/config.rb +55 -0
  60. data/lib/esa/subcontext_checker.rb +15 -0
  61. data/lib/esa/version.rb +3 -0
  62. data/lib/esa.rb +8 -0
  63. data/lib/generators/esa/USAGE +11 -0
  64. data/lib/generators/esa/esa_generator.rb +26 -0
  65. data/lib/generators/esa/templates/migration.rb +142 -0
  66. data/spec/factories/account_factory.rb +51 -0
  67. data/spec/factories/amount_factory.rb +19 -0
  68. data/spec/factories/chart_factory.rb +7 -0
  69. data/spec/factories/transaction_factory.rb +11 -0
  70. data/spec/lib/esa_spec.rb +0 -0
  71. data/spec/models/account_spec.rb +31 -0
  72. data/spec/models/amount_spec.rb +8 -0
  73. data/spec/models/asset_spec.rb +9 -0
  74. data/spec/models/chart_spec.rb +52 -0
  75. data/spec/models/credit_amount_spec.rb +9 -0
  76. data/spec/models/debit_amount_spec.rb +9 -0
  77. data/spec/models/equity_spec.rb +9 -0
  78. data/spec/models/expense_spec.rb +9 -0
  79. data/spec/models/liability_spec.rb +9 -0
  80. data/spec/models/revenue_spec.rb +9 -0
  81. data/spec/models/transaction_spec.rb +118 -0
  82. data/spec/rcov.opts +2 -0
  83. data/spec/spec.opts +4 -0
  84. data/spec/spec_helper.rb +16 -0
  85. data/spec/support/account_shared_examples.rb +57 -0
  86. data/spec/support/amount_shared_examples.rb +21 -0
  87. 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,11 @@
1
+ module ESA
2
+ module Contexts
3
+ class FilterContext < ESA::Context
4
+ attr_accessor :filters
5
+
6
+ def can_be_persisted?
7
+ false
8
+ end
9
+ end
10
+ end
11
+ 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