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