Class: ESA::Ruleset

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
Traits::Extendable
Defined in:
app/models/esa/ruleset.rb

Overview

The Ruleset class contains the business logic and rules of accounting.

Author:

  • Lenno Nagel

Instance Method Summary collapse

Instance Method Details

#accountables_updated_at(timespec) ⇒ Object

accountable



22
23
24
# File 'app/models/esa/ruleset.rb', line 22

def accountables_updated_at(timespec)
  []
end

#addable_unrecorded_events_as_attributes(accountable) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
# File 'app/models/esa/ruleset.rb', line 69

def addable_unrecorded_events_as_attributes(accountable)
  flag_times_max = accountable.esa_flags.group(:nature).maximum(:time)

  unrecorded_events_as_attributes(accountable).select do |event|
    event_flags = event_nature_flags[event[:nature]] || {}
    flag_times = flag_times_max.slice(*event_flags.keys.map(&:to_s))

    # allow when the event flags have not been used before or
    # when all the currently used flag times are before the new event
    flag_times.values.none? || flag_times.values.max <= event[:time]
  end
end

#ensure_positive_amounts(attrs) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'app/models/esa/ruleset.rb', line 198

def ensure_positive_amounts(attrs)
  amounts = attrs[:debits] + attrs[:credits]
  nonpositives = amounts.map{|a| a[:amount] <= BigDecimal(0)}

  if nonpositives.all?
    attrs.merge({
      debits: inverted(attrs[:credits]),
      credits: inverted(attrs[:debits]),
    })
  else
    attrs
  end
end

#event_flags_as_attributes(event) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
# File 'app/models/esa/ruleset.rb', line 92

def event_flags_as_attributes(event)
  flags = self.event_nature_flags[event.nature.to_sym] || {}
  flags.map do |nature,state|
    {
      :accountable => event.accountable,
      :nature => nature,
      :state => state,
      :event => event,
    }
  end
end

#event_nature_flagsObject

flags



88
89
90
# File 'app/models/esa/ruleset.rb', line 88

def event_nature_flags
  {}
end

#event_times(accountable) ⇒ Object

events



28
29
30
# File 'app/models/esa/ruleset.rb', line 28

def event_times(accountable)
  {}
end

#find_account(type, name) ⇒ Object



216
217
218
219
220
221
222
# File 'app/models/esa/ruleset.rb', line 216

def (type, name)
  if self.chart.present? and Account.valid_type?(type)
    Account.namespaced_type(type).constantize.
      where(:chart_id => self.chart, :name => name).
      first_or_create
  end
end

#flag_transactions(flag) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
# File 'app/models/esa/ruleset.rb', line 169

def flag_transactions(flag)
  if flag.adjusted?
    flag_transactions_when_adjusted(flag)
  elsif flag.is_set? and (flag.became_set? or (flag.event.present? and flag.event.nature.adjustment?))
    flag_transactions_when_set(flag)
  elsif flag.became_unset?
    flag_transactions_when_unset(flag)
  else
    []
  end
end

#flag_transactions_as_attributes(flag) ⇒ Object



181
182
183
184
185
186
187
188
189
190
191
# File 'app/models/esa/ruleset.rb', line 181

def flag_transactions_as_attributes(flag)
  defaults = {
    time: flag.time,
    accountable: flag.accountable,
    flag: flag,
  }
  flag_transactions(flag).map do |tx|
    attrs = defaults.merge(tx)
    ensure_positive_amounts(attrs)
  end
end

#flag_transactions_match_specs?(flag) ⇒ Boolean

Returns:

  • (Boolean)


193
194
195
196
# File 'app/models/esa/ruleset.rb', line 193

def flag_transactions_match_specs?(flag)
  specs = flag_transactions_as_attributes(flag)
  flag.transactions_match_specs?(specs)
end

#flag_transactions_spec(accountable, flag_nature) ⇒ Object

transactions



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'app/models/esa/ruleset.rb', line 118

def flag_transactions_spec(accountable, flag_nature)
  function_name = "flag_#{flag_nature}_transactions"

  if self.respond_to? function_name
    transactions = self.send(function_name, accountable)

    if transactions.is_a? Hash
      [transactions]
    elsif transactions.is_a? Array
      transactions
    else
      []
    end
  else
    []
  end
end

#flag_transactions_when_adjusted(flag) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'app/models/esa/ruleset.rb', line 150

def flag_transactions_when_adjusted(flag)
  flag.transactions.map do |tx|
    if tx.valid?
      spec = tx.spec
      [
        # original transaction, which must be kept
        spec,
        # adjustment transaction, which must be added
        spec.merge({
          :time => flag.adjustment_time,
          :description => "#{tx.description} / adjusted",
          :debits => spec[:credits], # swap
          :credits => spec[:debits], # swap
        })
      ]
    end
  end.compact.flatten
end

#flag_transactions_when_set(flag) ⇒ Object



136
137
138
# File 'app/models/esa/ruleset.rb', line 136

def flag_transactions_when_set(flag)
  flag_transactions_spec(flag.accountable, flag.nature)
end

#flag_transactions_when_unset(flag) ⇒ Object



140
141
142
143
144
145
146
147
148
# File 'app/models/esa/ruleset.rb', line 140

def flag_transactions_when_unset(flag)
  self.flag_transactions_when_set(flag).map do |tx|
    tx.merge({
      description: "#{tx[:description]} / reversed",
      debits: tx[:credits],
      credits: tx[:debits]
    })
  end
end

#flags_needing_adjustment(accountable) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
# File 'app/models/esa/ruleset.rb', line 104

def flags_needing_adjustment(accountable)
  natures = accountable.esa_flags.pluck(:nature).uniq.map{|nature| nature.to_sym}

  most_recent_flags = natures.map do |nature|
    accountable.esa_flags.transitioning.most_recent(nature)
  end.compact

  most_recent_flags.select do |flag|
    flag.is_set? and not flag_transactions_match_specs?(flag)
  end
end

#inverted(amounts) ⇒ Object



212
213
214
# File 'app/models/esa/ruleset.rb', line 212

def inverted(amounts)
  amounts.map{|a| a.dup.merge({amount: BigDecimal(0) - a[:amount]}) }
end

#is_adjustment_event_needed?(accountable) ⇒ Boolean

Returns:

  • (Boolean)


82
83
84
# File 'app/models/esa/ruleset.rb', line 82

def is_adjustment_event_needed?(accountable)
  flags_needing_adjustment(accountable).count > 0
end

#stateful_events(accountable) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'app/models/esa/ruleset.rb', line 32

def stateful_events(accountable)
  self.event_times(accountable).map do |nature,times|
    if times.present? and times.is_a? Time
      {nature: nature, time: times}
    elsif times.present? and times.respond_to? :each
      times.map do |t|
        if t.is_a? Time
          {nature: nature, time: t}
        else
          nil
        end
      end.compact
    else
      nil
    end
  end.flatten.compact
end

#stateful_events_as_attributes(accountable) ⇒ Object



50
51
52
53
54
55
56
57
58
# File 'app/models/esa/ruleset.rb', line 50

def stateful_events_as_attributes(accountable)
  defaults = {
    accountable: accountable,
    ruleset: self,
  }
  stateful_events(accountable).map do |event|
    defaults.merge(event)
  end
end

#unrecorded_events_as_attributes(accountable) ⇒ Object



60
61
62
63
64
65
66
67
# File 'app/models/esa/ruleset.rb', line 60

def unrecorded_events_as_attributes(accountable)
  stateful = stateful_events_as_attributes(accountable)

  recorded = accountable.esa_events.pluck([:nature, :time]).
        map{|nature,time| [nature, time.to_i]}

  stateful.reject{|s| [s[:nature].to_s, s[:time].to_i].in? recorded}
end