event_sourced_accounting 0.1.6 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.markdown +179 -2
- data/app/models/esa/account.rb +2 -2
- data/app/models/esa/chart.rb +2 -2
- data/app/models/esa/context.rb +21 -12
- data/app/models/esa/contexts/account_context.rb +2 -2
- data/app/models/esa/contexts/accountable_context.rb +1 -1
- data/app/models/esa/contexts/accountable_type_context.rb +1 -1
- data/app/models/esa/contexts/date_context.rb +22 -2
- data/app/models/esa/contexts/empty_context.rb +1 -1
- data/app/models/esa/event.rb +2 -2
- data/app/models/esa/flag.rb +2 -2
- data/app/models/esa/ruleset.rb +95 -29
- data/app/models/esa/transaction.rb +2 -2
- data/lib/esa/balance_checker.rb +1 -1
- data/lib/esa/blocking_processor.rb +38 -23
- data/lib/esa/configuration.rb +22 -8
- data/lib/esa/context_provider.rb +7 -2
- data/lib/esa/filters/accountable_filter.rb +10 -0
- data/lib/esa/filters/accountable_type_filter.rb +2 -0
- data/lib/esa/subcontext_checker.rb +17 -5
- data/lib/esa/version.rb +1 -1
- metadata +111 -110
@@ -34,16 +34,28 @@ module ESA
|
|
34
34
|
|
35
35
|
def self.produce_events(accountable)
|
36
36
|
ruleset = Ruleset.extension_instance(accountable)
|
37
|
+
|
37
38
|
if ruleset.present?
|
38
|
-
|
39
|
-
|
40
|
-
valid_events = unrecorded_events.select{|e| last_event_time.nil? or e[:time] >= last_event_time}
|
41
|
-
accountable.esa_events.new(valid_events)
|
39
|
+
events = ruleset.addable_unrecorded_events_as_attributes(accountable)
|
40
|
+
accountable.esa_events.new(events)
|
42
41
|
else
|
43
42
|
[]
|
44
43
|
end
|
45
44
|
end
|
46
45
|
|
46
|
+
def self.event_attrs_with_adjustment(ruleset, accountable, events)
|
47
|
+
if ruleset.is_adjustment_event_needed?(accountable)
|
48
|
+
events.unshift({
|
49
|
+
accountable: accountable,
|
50
|
+
ruleset: ruleset,
|
51
|
+
nature: :adjustment,
|
52
|
+
time: [events.map{|e| e[:time]}.min, Time.zone.now].compact.min,
|
53
|
+
})
|
54
|
+
else
|
55
|
+
events
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
47
59
|
def self.process_event(event)
|
48
60
|
flags_created = create_flags(event)
|
49
61
|
|
@@ -102,14 +114,7 @@ module ESA
|
|
102
114
|
flag.adjusted = true
|
103
115
|
flag.adjustment_time = event.time
|
104
116
|
|
105
|
-
|
106
|
-
:accountable => event.accountable,
|
107
|
-
:nature => flag.nature,
|
108
|
-
:state => flag.state,
|
109
|
-
:event => event,
|
110
|
-
}
|
111
|
-
|
112
|
-
adjustment = event.accountable.esa_flags.new(attrs)
|
117
|
+
adjustment = initialize_adjustment_flag(event, flag)
|
113
118
|
event.flags << adjustment
|
114
119
|
|
115
120
|
[flag, adjustment]
|
@@ -119,6 +124,17 @@ module ESA
|
|
119
124
|
end
|
120
125
|
end
|
121
126
|
|
127
|
+
def self.initialize_adjustment_flag(event, flag)
|
128
|
+
attrs = {
|
129
|
+
:accountable => event.accountable,
|
130
|
+
:nature => flag.nature,
|
131
|
+
:state => flag.state,
|
132
|
+
:event => event,
|
133
|
+
}
|
134
|
+
|
135
|
+
event.accountable.esa_flags.new(attrs)
|
136
|
+
end
|
137
|
+
|
122
138
|
def self.produce_flags_for_regular(event)
|
123
139
|
if event.ruleset.present?
|
124
140
|
existing_flags = event.flags.all
|
@@ -157,19 +173,18 @@ module ESA
|
|
157
173
|
end
|
158
174
|
|
159
175
|
def self.produce_transactions(flag)
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
required_transactions.map do |attrs|
|
165
|
-
transaction = existing_transactions.find{|tx| tx.matches_spec?(attrs)}
|
176
|
+
txs = flag.accountable.esa_transactions.new(missing_transactions(flag))
|
177
|
+
flag.transactions += txs
|
178
|
+
txs
|
179
|
+
end
|
166
180
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
181
|
+
def self.missing_transactions(flag)
|
182
|
+
if flag.ruleset.present? and flag.transition.present? and flag.transition.in? [-1, 0, 1]
|
183
|
+
existing = flag.transactions.all
|
184
|
+
required = flag.ruleset.flag_transactions_as_attributes(flag)
|
171
185
|
|
172
|
-
|
186
|
+
required.reject do |attrs|
|
187
|
+
existing.find{|tx| tx.matches_spec?(attrs)}.present?
|
173
188
|
end
|
174
189
|
else
|
175
190
|
[]
|
data/lib/esa/configuration.rb
CHANGED
@@ -16,6 +16,7 @@ module ESA
|
|
16
16
|
attr_accessor :context_freshness_threshold
|
17
17
|
attr_accessor :context_providers
|
18
18
|
attr_accessor :context_tree
|
19
|
+
attr_accessor :context_walk_ignore
|
19
20
|
|
20
21
|
def initialize
|
21
22
|
@base_classes = [ESA::Ruleset, ESA::Event, ESA::Flag, ESA::Transaction].freeze
|
@@ -34,25 +35,37 @@ module ESA
|
|
34
35
|
'account' => ESA::ContextProviders::AccountContextProvider,
|
35
36
|
'accountable' => ESA::ContextProviders::AccountableContextProvider,
|
36
37
|
'accountable_type' => ESA::ContextProviders::AccountableTypeContextProvider,
|
37
|
-
'
|
38
|
-
'
|
38
|
+
'month' => [ESA::ContextProviders::DateContextProvider, {period: :month}],
|
39
|
+
'date' => [ESA::ContextProviders::DateContextProvider, {period: :date}],
|
39
40
|
}
|
40
41
|
|
41
42
|
@context_tree = {
|
42
43
|
'account' => {
|
43
|
-
'
|
44
|
-
'
|
44
|
+
'month' => {
|
45
|
+
'date' => {},
|
45
46
|
},
|
46
47
|
},
|
47
|
-
'
|
48
|
+
'period' => {
|
48
49
|
'account' => {
|
49
|
-
'
|
50
|
+
'date' => {},
|
50
51
|
},
|
51
52
|
},
|
52
|
-
'
|
53
|
+
'year' => {
|
54
|
+
'account' => {
|
55
|
+
'date' => {},
|
56
|
+
},
|
57
|
+
},
|
58
|
+
'month' => {
|
59
|
+
'account' => {
|
60
|
+
'date' => {},
|
61
|
+
},
|
62
|
+
},
|
63
|
+
'date' => {
|
53
64
|
'account' => {},
|
54
65
|
},
|
55
66
|
}
|
67
|
+
|
68
|
+
@context_walk_ignore = ['filter']
|
56
69
|
end
|
57
70
|
|
58
71
|
def register(accountable, short_name=nil)
|
@@ -83,7 +96,8 @@ module ESA
|
|
83
96
|
end
|
84
97
|
|
85
98
|
def context_providers_for_path(path=[])
|
86
|
-
|
99
|
+
clean_path = path - context_walk_ignore
|
100
|
+
@context_providers.slice(*self.walk_context_tree(clean_path).keys)
|
87
101
|
end
|
88
102
|
end
|
89
103
|
end
|
data/lib/esa/context_provider.rb
CHANGED
@@ -38,7 +38,8 @@ module ESA
|
|
38
38
|
|
39
39
|
def self.contained_subcontexts(context, namespace, existing, options = {})
|
40
40
|
contained_ids = contained_ids(context, options)
|
41
|
-
|
41
|
+
existing_grouped = existing.group_by{|sub| context_id(sub, options)}
|
42
|
+
existing_ids = existing_grouped.keys
|
42
43
|
|
43
44
|
new_ids = contained_ids - existing_ids
|
44
45
|
|
@@ -46,7 +47,11 @@ module ESA
|
|
46
47
|
instantiate(context, namespace, id, options)
|
47
48
|
end
|
48
49
|
|
49
|
-
|
50
|
+
keep_subcontexts = existing_grouped.map do |id,group|
|
51
|
+
(id.in? contained_ids) ? group.first : nil
|
52
|
+
end.compact
|
53
|
+
|
54
|
+
new_subcontexts + keep_subcontexts
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|
@@ -34,7 +34,12 @@ module ESA
|
|
34
34
|
scope :with_accountable, lambda { |accountable,type|
|
35
35
|
joins(:transaction).where(esa_transactions: {accountable_id: accountable, accountable_type: type})
|
36
36
|
}
|
37
|
+
scope :excl_accountable, lambda { |accountable,type|
|
38
|
+
joins(:transaction).where(ESA::Transaction.arel_table[:accountable_id].not_eq(accountable), ESA::Transaction.arel_table[:accountable_type].not_eq(type))
|
39
|
+
}
|
40
|
+
|
37
41
|
scope :with_accountable_def, lambda { |definitions| joins(:transaction).joins("INNER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `esa_transactions`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `esa_transactions`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`") }
|
42
|
+
scope :excl_accountable_def, lambda { |definitions| joins(:transaction).joins("LEFT OUTER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `esa_transactions`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `esa_transactions`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`").where("`accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` IS NULL") }
|
38
43
|
end
|
39
44
|
end
|
40
45
|
|
@@ -45,7 +50,12 @@ module ESA
|
|
45
50
|
scope :with_accountable, lambda { |accountable,type|
|
46
51
|
where(accountable_id: accountable, accountable_type: type)
|
47
52
|
}
|
53
|
+
scope :excl_accountable, lambda { |accountable,type|
|
54
|
+
where(arel_table[:accountable_id].not_eq(accountable), arel_table[:accountable_type].not_eq(type))
|
55
|
+
}
|
56
|
+
|
48
57
|
scope :with_accountable_def, lambda { |definitions| joins("INNER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `#{table_name}`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `#{table_name}`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`") }
|
58
|
+
scope :excl_accountable_def, lambda { |definitions| joins("LEFT OUTER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `#{table_name}`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `#{table_name}`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`").where("`accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` IS NULL") }
|
49
59
|
end
|
50
60
|
end
|
51
61
|
end
|
@@ -6,6 +6,7 @@ module ESA
|
|
6
6
|
|
7
7
|
included do
|
8
8
|
scope :with_accountable_type, lambda { |type| joins(:transaction).where(esa_transactions: {accountable_type: type}) }
|
9
|
+
scope :excl_accountable_type, lambda { |type| joins(:transaction).where(ESA::Transaction.arel_table[:accountable_type].not_eq(type)) }
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
@@ -14,6 +15,7 @@ module ESA
|
|
14
15
|
|
15
16
|
included do
|
16
17
|
scope :with_accountable_type, lambda { |type| where(accountable_type: type) }
|
18
|
+
scope :excl_accountable_type, lambda { |type| where(arel_table[:accountable_type].not_eq(type)) }
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
@@ -1,13 +1,25 @@
|
|
1
1
|
module ESA
|
2
2
|
class SubcontextChecker
|
3
|
-
def self.
|
4
|
-
|
3
|
+
def self.providers(context, options = {})
|
4
|
+
if :namespace.in? options
|
5
|
+
if options[:namespace].respond_to? :each
|
6
|
+
ESA.configuration.context_providers.slice(*options[:namespace])
|
7
|
+
else
|
8
|
+
ESA.configuration.context_providers.slice("#{options[:namespace]}")
|
9
|
+
end
|
10
|
+
else
|
11
|
+
ESA.configuration.context_providers_for_path(context.effective_path)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.check(context, options = {})
|
16
|
+
providers(context, options).each do |namespace,provider|
|
5
17
|
if provider.is_a? Class and provider.respond_to? :check_subcontexts
|
6
18
|
provider.check_subcontexts(context, namespace)
|
7
|
-
elsif provider.respond_to? :count and provider.count == 2 and
|
19
|
+
elsif provider.respond_to? :count and provider.count == 2 and
|
8
20
|
provider[0].is_a? Class and provider[0].respond_to? :check_subcontexts and provider[1].is_a? Hash
|
9
|
-
|
10
|
-
|
21
|
+
provider_klass, provider_options = provider
|
22
|
+
provider_klass.check_subcontexts(context, namespace, provider_options)
|
11
23
|
end
|
12
24
|
end
|
13
25
|
end
|
data/lib/esa/version.rb
CHANGED