double_entry 1.0.1 → 2.0.0.beta5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +497 -0
- data/README.md +107 -44
- data/double_entry.gemspec +22 -49
- data/lib/active_record/locking_extensions.rb +3 -3
- data/lib/active_record/locking_extensions/log_subscriber.rb +1 -1
- data/lib/double_entry.rb +29 -21
- data/lib/double_entry/account.rb +39 -46
- data/lib/double_entry/account_balance.rb +20 -3
- data/lib/double_entry/balance_calculator.rb +5 -5
- data/lib/double_entry/configurable.rb +1 -0
- data/lib/double_entry/configuration.rb +8 -2
- data/lib/double_entry/errors.rb +13 -13
- data/lib/double_entry/line.rb +7 -6
- data/lib/double_entry/locking.rb +5 -5
- data/lib/double_entry/transfer.rb +37 -30
- data/lib/double_entry/validation.rb +1 -0
- data/lib/double_entry/validation/account_fixer.rb +36 -0
- data/lib/double_entry/validation/line_check.rb +25 -43
- data/lib/double_entry/version.rb +1 -1
- data/lib/generators/double_entry/install/install_generator.rb +22 -1
- data/lib/generators/double_entry/install/templates/initializer.rb +20 -0
- data/lib/generators/double_entry/install/templates/migration.rb +45 -55
- metadata +35 -256
- data/.gitignore +0 -32
- data/.rspec +0 -2
- data/.travis.yml +0 -29
- data/.yardopts +0 -2
- data/Gemfile +0 -2
- data/Rakefile +0 -15
- data/lib/double_entry/reporting.rb +0 -181
- data/lib/double_entry/reporting/aggregate.rb +0 -110
- data/lib/double_entry/reporting/aggregate_array.rb +0 -76
- data/lib/double_entry/reporting/day_range.rb +0 -42
- data/lib/double_entry/reporting/hour_range.rb +0 -45
- data/lib/double_entry/reporting/line_aggregate.rb +0 -16
- data/lib/double_entry/reporting/line_aggregate_filter.rb +0 -79
- data/lib/double_entry/reporting/month_range.rb +0 -94
- data/lib/double_entry/reporting/time_range.rb +0 -59
- data/lib/double_entry/reporting/time_range_array.rb +0 -49
- data/lib/double_entry/reporting/week_range.rb +0 -107
- data/lib/double_entry/reporting/year_range.rb +0 -40
- data/script/jack_hammer +0 -210
- data/script/setup.sh +0 -8
- data/spec/active_record/locking_extensions_spec.rb +0 -110
- data/spec/double_entry/account_balance_spec.rb +0 -7
- data/spec/double_entry/account_spec.rb +0 -130
- data/spec/double_entry/balance_calculator_spec.rb +0 -88
- data/spec/double_entry/configuration_spec.rb +0 -50
- data/spec/double_entry/line_spec.rb +0 -80
- data/spec/double_entry/locking_spec.rb +0 -214
- data/spec/double_entry/performance/double_entry_performance_spec.rb +0 -32
- data/spec/double_entry/performance/reporting/aggregate_performance_spec.rb +0 -50
- data/spec/double_entry/reporting/aggregate_array_spec.rb +0 -123
- data/spec/double_entry/reporting/aggregate_spec.rb +0 -205
- data/spec/double_entry/reporting/line_aggregate_filter_spec.rb +0 -90
- data/spec/double_entry/reporting/line_aggregate_spec.rb +0 -39
- data/spec/double_entry/reporting/month_range_spec.rb +0 -139
- data/spec/double_entry/reporting/time_range_array_spec.rb +0 -169
- data/spec/double_entry/reporting/time_range_spec.rb +0 -45
- data/spec/double_entry/reporting/week_range_spec.rb +0 -103
- data/spec/double_entry/reporting_spec.rb +0 -181
- data/spec/double_entry/transfer_spec.rb +0 -93
- data/spec/double_entry/validation/line_check_spec.rb +0 -99
- data/spec/double_entry_spec.rb +0 -428
- data/spec/generators/double_entry/install/install_generator_spec.rb +0 -30
- data/spec/spec_helper.rb +0 -118
- data/spec/support/accounts.rb +0 -21
- data/spec/support/blueprints.rb +0 -43
- data/spec/support/database.example.yml +0 -21
- data/spec/support/database.travis.yml +0 -24
- data/spec/support/double_entry_spec_helper.rb +0 -27
- data/spec/support/gemfiles/Gemfile.rails-3.2.x +0 -8
- data/spec/support/gemfiles/Gemfile.rails-4.1.x +0 -6
- data/spec/support/gemfiles/Gemfile.rails-4.2.x +0 -5
- data/spec/support/gemfiles/Gemfile.rails-5.0.x +0 -5
- data/spec/support/performance_helper.rb +0 -26
- data/spec/support/reporting_configuration.rb +0 -6
- data/spec/support/schema.rb +0 -74
data/.gitignore
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
*.gem
|
2
|
-
*.rbc
|
3
|
-
/.config
|
4
|
-
/coverage/
|
5
|
-
/InstalledFiles
|
6
|
-
/pkg/
|
7
|
-
/spec/reports/
|
8
|
-
/test/tmp/
|
9
|
-
/test/version_tmp/
|
10
|
-
/tmp/
|
11
|
-
|
12
|
-
## Performance profiling
|
13
|
-
/profiles/
|
14
|
-
|
15
|
-
## Documentation cache and generated files:
|
16
|
-
/.yardoc/
|
17
|
-
/_yardoc/
|
18
|
-
/doc/
|
19
|
-
/rdoc/
|
20
|
-
|
21
|
-
## Environment normalisation:
|
22
|
-
/.bundle/
|
23
|
-
/lib/bundler/man/
|
24
|
-
/Gemfile.lock
|
25
|
-
/.ruby-version
|
26
|
-
/.ruby-gemset
|
27
|
-
|
28
|
-
# DoubleEntry specific
|
29
|
-
/bin/
|
30
|
-
/log/
|
31
|
-
/spec/reports/
|
32
|
-
/spec/support/database.yml
|
data/.rspec
DELETED
data/.travis.yml
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
sudo: false
|
3
|
-
before_script:
|
4
|
-
- cp spec/support/database.travis.yml spec/support/database.yml
|
5
|
-
- mysql -e 'create database double_entry_test;'
|
6
|
-
- psql -c 'create database double_entry_test;' -U postgres
|
7
|
-
script:
|
8
|
-
- bundle exec rake spec
|
9
|
-
- ruby script/jack_hammer -t 2000
|
10
|
-
matrix:
|
11
|
-
include:
|
12
|
-
- rvm: 2.1
|
13
|
-
gemfile: spec/support/gemfiles/Gemfile.rails-3.2.x
|
14
|
-
env: DB=mysql
|
15
|
-
- rvm: 2.2
|
16
|
-
gemfile: spec/support/gemfiles/Gemfile.rails-4.1.x
|
17
|
-
env: DB=mysql
|
18
|
-
- rvm: 2.2
|
19
|
-
gemfile: spec/support/gemfiles/Gemfile.rails-4.2.x
|
20
|
-
env: DB=mysql
|
21
|
-
- rvm: 2.3
|
22
|
-
gemfile: spec/support/gemfiles/Gemfile.rails-5.0.x
|
23
|
-
env: DB=mysql
|
24
|
-
- rvm: 2.3
|
25
|
-
gemfile: spec/support/gemfiles/Gemfile.rails-5.0.x
|
26
|
-
env: DB=sqlite
|
27
|
-
- rvm: 2.3
|
28
|
-
gemfile: spec/support/gemfiles/Gemfile.rails-5.0.x
|
29
|
-
env: DB=postgres
|
data/.yardopts
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'rspec/core/rake_task'
|
2
|
-
require 'bundler/gem_tasks'
|
3
|
-
|
4
|
-
RSpec::Core::RakeTask.new(:spec) do |t|
|
5
|
-
t.verbose = false
|
6
|
-
t.ruby_opts = '-w'
|
7
|
-
end
|
8
|
-
|
9
|
-
task :default do
|
10
|
-
%w(mysql postgres sqlite).each do |db|
|
11
|
-
puts "Running tests with `DB=#{db}`"
|
12
|
-
ENV['DB'] = db
|
13
|
-
Rake::Task['spec'].execute
|
14
|
-
end
|
15
|
-
end
|
@@ -1,181 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'double_entry/reporting/aggregate'
|
3
|
-
require 'double_entry/reporting/aggregate_array'
|
4
|
-
require 'double_entry/reporting/time_range'
|
5
|
-
require 'double_entry/reporting/day_range'
|
6
|
-
require 'double_entry/reporting/hour_range'
|
7
|
-
require 'double_entry/reporting/week_range'
|
8
|
-
require 'double_entry/reporting/month_range'
|
9
|
-
require 'double_entry/reporting/year_range'
|
10
|
-
require 'double_entry/reporting/line_aggregate'
|
11
|
-
require 'double_entry/reporting/line_aggregate_filter'
|
12
|
-
require 'double_entry/reporting/time_range_array'
|
13
|
-
|
14
|
-
module DoubleEntry
|
15
|
-
# @api private
|
16
|
-
module Reporting
|
17
|
-
include Configurable
|
18
|
-
extend self
|
19
|
-
|
20
|
-
class Configuration
|
21
|
-
attr_accessor :start_of_business, :first_month_of_financial_year
|
22
|
-
|
23
|
-
def initialize #:nodoc:
|
24
|
-
@start_of_business = Time.new(1970, 1, 1)
|
25
|
-
@first_month_of_financial_year = 7
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
class AggregateFunctionNotSupported < RuntimeError; end
|
30
|
-
|
31
|
-
# Perform an aggregate calculation on a set of transfers for an account.
|
32
|
-
#
|
33
|
-
# The transfers included in the calculation can be limited by time range
|
34
|
-
# and provided custom filters.
|
35
|
-
#
|
36
|
-
# @example Find the sum for all $10 :save transfers in all :checking accounts in the current month, made by Australian users (assume the date is January 30, 2014).
|
37
|
-
# time_range = DoubleEntry::Reporting::TimeRange.make(2014, 1)
|
38
|
-
#
|
39
|
-
# DoubleEntry::Line.class_eval do
|
40
|
-
# scope :specific_transfer_amount, ->(amount) { where(:amount => amount.fractional) }
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# DoubleEntry::Reporting.aggregate(
|
44
|
-
# :sum,
|
45
|
-
# :checking,
|
46
|
-
# :save,
|
47
|
-
# time_range,
|
48
|
-
# :filter => [
|
49
|
-
# :scope => {
|
50
|
-
# :name => :specific_transfer_amount,
|
51
|
-
# :arguments => [Money.new(10_00)]
|
52
|
-
# },
|
53
|
-
# :metadata => {
|
54
|
-
# :user_location => 'AU'
|
55
|
-
# },
|
56
|
-
# ]
|
57
|
-
# )
|
58
|
-
# @param [Symbol] function The function to perform on the set of transfers.
|
59
|
-
# Valid functions are :sum, :count, and :average
|
60
|
-
# @param [Symbol] account The symbol identifying the account to perform
|
61
|
-
# the aggregate calculation on. As specified in the account configuration.
|
62
|
-
# @param [Symbol] code The application specific code for the type of
|
63
|
-
# transfer to perform an aggregate calculation on. As specified in the
|
64
|
-
# transfer configuration.
|
65
|
-
# @param [DoubleEntry::Reporting::TimeRange] Only include transfers in the
|
66
|
-
# given time range in the calculation.
|
67
|
-
# @option options :filter [Array<Hash<Symbol,Hash<Symbol,Object>>>]
|
68
|
-
# An array of custom filter to apply before performing the aggregate
|
69
|
-
# calculation. Filters can be either scope filters, where the name must be
|
70
|
-
# specified, or they can be metadata filters, where the key/value pair to
|
71
|
-
# match on must be specified.
|
72
|
-
# Scope filters must be monkey patched as scopes into the DoubleEntry::Line
|
73
|
-
# class, as the example above shows. Scope filters may also take a list of
|
74
|
-
# arguments to pass into the monkey patched scope, and, if provided, must
|
75
|
-
# be contained within an array.
|
76
|
-
# @return [Money, Fixnum] Returns a Money object for :sum and :average
|
77
|
-
# calculations, or a Fixnum for :count calculations.
|
78
|
-
# @raise [Reporting::AggregateFunctionNotSupported] The provided function
|
79
|
-
# is not supported.
|
80
|
-
#
|
81
|
-
def aggregate(function, account, code, range, options = {})
|
82
|
-
Aggregate.formatted_amount(function, account, code, range, options)
|
83
|
-
end
|
84
|
-
|
85
|
-
# Perform an aggregate calculation on a set of transfers for an account
|
86
|
-
# and return the results in an array partitioned by a time range type.
|
87
|
-
#
|
88
|
-
# The transfers included in the calculation can be limited by a time range
|
89
|
-
# and provided custom filters.
|
90
|
-
#
|
91
|
-
# @example Find the number of all $10 :save transfers in all :checking accounts per month for the entire year (Assume the year is 2014).
|
92
|
-
# DoubleEntry::Reporting.aggregate_array(
|
93
|
-
# :sum,
|
94
|
-
# :checking,
|
95
|
-
# :save,
|
96
|
-
# :range_type => 'month',
|
97
|
-
# :start => '2014-01-01',
|
98
|
-
# :finish => '2014-12-31',
|
99
|
-
# )
|
100
|
-
# @param [Symbol] function The function to perform on the set of transfers.
|
101
|
-
# Valid functions are :sum, :count, and :average
|
102
|
-
# @param [Symbol] account The symbol identifying the account to perform
|
103
|
-
# the aggregate calculation on. As specified in the account configuration.
|
104
|
-
# @param [Symbol] code The application specific code for the type of
|
105
|
-
# transfer to perform an aggregate calculation on. As specified in the
|
106
|
-
# transfer configuration.
|
107
|
-
# @option options :filter [Array<Symbol>, Array<Hash<Symbol, Object>>]
|
108
|
-
# A custom filter to apply before performing the aggregate calculation.
|
109
|
-
# Currently, filters must be monkey patched as scopes into the
|
110
|
-
# DoubleEntry::Line class in order to be used as filters, as the example
|
111
|
-
# shows. If the filter requires a parameter, it must be given in a Hash,
|
112
|
-
# otherwise pass an array with the symbol names for the defined scopes.
|
113
|
-
# @option options :range_type [String] The type of time range to return data
|
114
|
-
# for. For example, specifying 'month' will return an array of the resulting
|
115
|
-
# aggregate calculation for each month.
|
116
|
-
# Valid range_types are 'hour', 'day', 'week', 'month', and 'year'
|
117
|
-
# @option options :start [String] The start date for the time range to perform
|
118
|
-
# calculations in. The default start date is the start_of_business (can
|
119
|
-
# be specified in configuration).
|
120
|
-
# The format of the string must be as follows: 'YYYY-mm-dd'
|
121
|
-
# @option options :finish [String] The finish (or end) date for the time range
|
122
|
-
# to perform calculations in. The default finish date is the current date.
|
123
|
-
# The format of the string must be as follows: 'YYYY-mm-dd'
|
124
|
-
# @return [Array<Money, Fixnum>] Returns an array of Money objects for :sum
|
125
|
-
# and :average calculations, or an array of Fixnum for :count calculations.
|
126
|
-
# The array is indexed by the range_type. For example, if range_type is
|
127
|
-
# specified as 'month', each index in the array will represent a month.
|
128
|
-
# @raise [Reporting::AggregateFunctionNotSupported] The provided function
|
129
|
-
# is not supported.
|
130
|
-
#
|
131
|
-
def aggregate_array(function, account, code, options = {})
|
132
|
-
AggregateArray.new(function, account, code, options)
|
133
|
-
end
|
134
|
-
|
135
|
-
# Identify the scopes with the given account identifier holding at least
|
136
|
-
# the provided minimum balance.
|
137
|
-
#
|
138
|
-
# @example Find users with at least $1,000,000 in their savings accounts
|
139
|
-
# DoubleEntry::Reporting.scopes_with_minimum_balance_for_account(
|
140
|
-
# 1_000_000.dollars,
|
141
|
-
# :savings,
|
142
|
-
# ) # might return the user ids: [ 1423, 12232, 34729 ]
|
143
|
-
# @param [Money] minimum_balance Minimum account balance a scope must have
|
144
|
-
# to be included in the result set.
|
145
|
-
# @param [Symbol] account_identifier
|
146
|
-
# @return [Array<Fixnum>] Scopes
|
147
|
-
#
|
148
|
-
def scopes_with_minimum_balance_for_account(minimum_balance, account_identifier)
|
149
|
-
select_values(sanitize_sql_array([<<-SQL, account_identifier, minimum_balance.cents])).map(&:to_i)
|
150
|
-
SELECT scope
|
151
|
-
FROM #{AccountBalance.table_name}
|
152
|
-
WHERE account = ?
|
153
|
-
AND balance >= ?
|
154
|
-
SQL
|
155
|
-
end
|
156
|
-
|
157
|
-
# This is used by the concurrency test script.
|
158
|
-
#
|
159
|
-
# @api private
|
160
|
-
# @return [Boolean] true if all the amounts for an account add up to the final balance,
|
161
|
-
# which they always should.
|
162
|
-
#
|
163
|
-
def reconciled?(account)
|
164
|
-
scoped_lines = Line.where(:account => "#{account.identifier}")
|
165
|
-
scoped_lines = scoped_lines.where(:scope => "#{account.scope_identity}") if account.scoped?
|
166
|
-
sum_of_amounts = scoped_lines.sum(:amount)
|
167
|
-
final_balance = scoped_lines.order(:id).last[:balance]
|
168
|
-
cached_balance = AccountBalance.find_by_account(account)[:balance]
|
169
|
-
final_balance == sum_of_amounts && final_balance == cached_balance
|
170
|
-
end
|
171
|
-
|
172
|
-
private
|
173
|
-
|
174
|
-
delegate :connection, :to => ActiveRecord::Base
|
175
|
-
delegate :select_values, :to => :connection
|
176
|
-
|
177
|
-
def sanitize_sql_array(sql_array)
|
178
|
-
ActiveRecord::Base.send(:sanitize_sql_array, sql_array)
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
@@ -1,110 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module DoubleEntry
|
3
|
-
module Reporting
|
4
|
-
class Aggregate
|
5
|
-
attr_reader :function, :account, :code, :range, :filter, :currency
|
6
|
-
|
7
|
-
def self.formatted_amount(function, account, code, range, options = {})
|
8
|
-
new(function, account, code, range, options).formatted_amount
|
9
|
-
end
|
10
|
-
|
11
|
-
def initialize(function, account, code, range, options = {})
|
12
|
-
@function = function.to_s
|
13
|
-
fail AggregateFunctionNotSupported unless %w(sum count average).include?(@function)
|
14
|
-
|
15
|
-
@account = account
|
16
|
-
@code = code ? code.to_s : nil
|
17
|
-
@range = range
|
18
|
-
@filter = options[:filter]
|
19
|
-
@currency = DoubleEntry::Account.currency(account)
|
20
|
-
end
|
21
|
-
|
22
|
-
def amount(force_recalculation = false)
|
23
|
-
if force_recalculation
|
24
|
-
clear_old_aggregates
|
25
|
-
calculate
|
26
|
-
else
|
27
|
-
retrieve || calculate
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def formatted_amount(value = amount)
|
32
|
-
value ||= 0
|
33
|
-
if function == 'count'
|
34
|
-
value
|
35
|
-
else
|
36
|
-
Money.new(value, currency)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def retrieve
|
43
|
-
aggregate = LineAggregate.where(field_hash).first
|
44
|
-
aggregate.amount if aggregate
|
45
|
-
end
|
46
|
-
|
47
|
-
def clear_old_aggregates
|
48
|
-
LineAggregate.delete_all(field_hash)
|
49
|
-
end
|
50
|
-
|
51
|
-
def calculate
|
52
|
-
if range.class == YearRange
|
53
|
-
aggregate = calculate_yearly_aggregate
|
54
|
-
else
|
55
|
-
aggregate = LineAggregate.aggregate(function, account, code, range, filter)
|
56
|
-
end
|
57
|
-
|
58
|
-
if range_is_complete?
|
59
|
-
fields = field_hash
|
60
|
-
fields[:amount] = aggregate || 0
|
61
|
-
LineAggregate.create! fields
|
62
|
-
end
|
63
|
-
|
64
|
-
aggregate
|
65
|
-
end
|
66
|
-
|
67
|
-
def calculate_yearly_aggregate
|
68
|
-
# We calculate yearly aggregates by combining monthly aggregates
|
69
|
-
# otherwise they will get excruciatingly slow to calculate
|
70
|
-
# as the year progresses. (I am thinking mainly of the 'current' year.)
|
71
|
-
# Combining monthly aggregates will mean that the figure will be partially memoized
|
72
|
-
if function == 'average'
|
73
|
-
calculate_yearly_average
|
74
|
-
else
|
75
|
-
result = (1..12).inject(formatted_amount(0)) do |total, month|
|
76
|
-
total + Aggregate.new(function, account, code, MonthRange.new(:year => range.year, :month => month), :filter => filter).formatted_amount
|
77
|
-
end
|
78
|
-
result.is_a?(Money) ? result.cents : result
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def calculate_yearly_average
|
83
|
-
# need this seperate function, because an average of averages is not the correct average
|
84
|
-
year_range = YearRange.new(:year => range.year)
|
85
|
-
sum = Aggregate.new(:sum, account, code, year_range, :filter => filter).formatted_amount
|
86
|
-
count = Aggregate.new(:count, account, code, year_range, :filter => filter).formatted_amount
|
87
|
-
(count == 0) ? 0 : (sum / count).cents
|
88
|
-
end
|
89
|
-
|
90
|
-
def range_is_complete?
|
91
|
-
Time.now > range.finish
|
92
|
-
end
|
93
|
-
|
94
|
-
def field_hash
|
95
|
-
{
|
96
|
-
:function => function,
|
97
|
-
:account => account,
|
98
|
-
:code => code,
|
99
|
-
:year => range.year,
|
100
|
-
:month => range.month,
|
101
|
-
:week => range.week,
|
102
|
-
:day => range.day,
|
103
|
-
:hour => range.hour,
|
104
|
-
:filter => filter.inspect,
|
105
|
-
:range_type => range.range_type.to_s,
|
106
|
-
}
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
@@ -1,76 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module DoubleEntry
|
3
|
-
module Reporting
|
4
|
-
class AggregateArray < Array
|
5
|
-
# An AggregateArray is awesome
|
6
|
-
# It is useful for making reports
|
7
|
-
# It is basically an array of aggregate results,
|
8
|
-
# representing a column of data in a report.
|
9
|
-
#
|
10
|
-
# For example, you could request all sales
|
11
|
-
# broken down by month and it would return an array of values
|
12
|
-
attr_reader :function, :account, :code, :filter, :range_type, :start, :finish, :currency
|
13
|
-
|
14
|
-
def initialize(function, account, code, options)
|
15
|
-
@function = function.to_s
|
16
|
-
@account = account
|
17
|
-
@code = code
|
18
|
-
@filter = options[:filter]
|
19
|
-
@range_type = options[:range_type]
|
20
|
-
@start = options[:start]
|
21
|
-
@finish = options[:finish]
|
22
|
-
@currency = DoubleEntry::Account.currency(account)
|
23
|
-
|
24
|
-
retrieve_aggregates
|
25
|
-
fill_in_missing_aggregates
|
26
|
-
populate_self
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def populate_self
|
32
|
-
all_periods.each do |period|
|
33
|
-
self << @aggregates[period.key]
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def fill_in_missing_aggregates
|
38
|
-
# some aggregates may not have been previously calculated, so we can request them now
|
39
|
-
# (this includes aggregates for the still-running period)
|
40
|
-
all_periods.each do |period|
|
41
|
-
unless @aggregates[period.key]
|
42
|
-
@aggregates[period.key] = Aggregate.formatted_amount(function, account, code, period, :filter => filter)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# get any previously calculated aggregates
|
48
|
-
def retrieve_aggregates
|
49
|
-
fail ArgumentError, "Invalid range type '#{range_type}'" unless %w(year month week day hour).include? range_type
|
50
|
-
scope = LineAggregate.
|
51
|
-
where(:function => function).
|
52
|
-
where(:range_type => 'normal').
|
53
|
-
where(:account => account.to_s).
|
54
|
-
where(:code => code.to_s).
|
55
|
-
where(:filter => filter.inspect).
|
56
|
-
where(LineAggregate.arel_table[range_type].not_eq(nil))
|
57
|
-
@aggregates = scope.each_with_object({}) do |result, hash|
|
58
|
-
hash[result.key] = formatted_amount(result.amount)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def all_periods
|
63
|
-
TimeRangeArray.make(range_type, start, finish)
|
64
|
-
end
|
65
|
-
|
66
|
-
def formatted_amount(amount)
|
67
|
-
amount ||= 0
|
68
|
-
if function == 'count'
|
69
|
-
amount
|
70
|
-
else
|
71
|
-
Money.new(amount, currency)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|