double_entry 1.0.1 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +432 -0
- data/README.md +36 -9
- data/double_entry.gemspec +20 -48
- data/lib/active_record/locking_extensions.rb +3 -3
- data/lib/active_record/locking_extensions/log_subscriber.rb +1 -1
- data/lib/double_entry/account.rb +38 -45
- data/lib/double_entry/account_balance.rb +18 -1
- data/lib/double_entry/errors.rb +13 -13
- data/lib/double_entry/line.rb +3 -2
- data/lib/double_entry/reporting.rb +26 -38
- data/lib/double_entry/reporting/aggregate.rb +43 -23
- data/lib/double_entry/reporting/aggregate_array.rb +16 -13
- data/lib/double_entry/reporting/line_aggregate.rb +3 -2
- data/lib/double_entry/reporting/line_aggregate_filter.rb +8 -10
- data/lib/double_entry/reporting/line_metadata_filter.rb +33 -0
- data/lib/double_entry/transfer.rb +33 -27
- 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 +22 -40
- data/lib/double_entry/version.rb +1 -1
- data/lib/generators/double_entry/install/install_generator.rb +7 -1
- data/lib/generators/double_entry/install/templates/migration.rb +27 -25
- metadata +33 -243
- 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/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/double_entry.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
|
@@ -7,67 +7,39 @@ require 'double_entry/version'
|
|
7
7
|
Gem::Specification.new do |gem|
|
8
8
|
gem.name = 'double_entry'
|
9
9
|
gem.version = DoubleEntry::VERSION
|
10
|
-
gem.authors = ['
|
11
|
-
gem.email = ['
|
10
|
+
gem.authors = ['Envato']
|
11
|
+
gem.email = ['rubygems@envato.com']
|
12
12
|
gem.summary = 'Tools to build your double entry financial ledger'
|
13
13
|
gem.homepage = 'https://github.com/envato/double_entry'
|
14
14
|
|
15
|
-
gem.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
Please note the following changes in DoubleEntry:
|
22
|
-
- New table `double_entry_line_metadata` has been introduced and is *required* for
|
23
|
-
aggregate reporting filtering to work. Existing applications must manually manage
|
24
|
-
this change via a migration similar to the following:
|
25
|
-
|
26
|
-
class CreateDoubleEntryLineMetadata < ActiveRecord::Migration
|
27
|
-
def self.up
|
28
|
-
create_table "#{DoubleEntry.table_name_prefix}line_metadata", :force => true do |t|
|
29
|
-
t.integer "line_id", :null => false
|
30
|
-
t.string "key", :limit => 48, :null => false
|
31
|
-
t.string "value", :limit => 64, :null => false
|
32
|
-
t.timestamps :null => false
|
33
|
-
end
|
34
|
-
|
35
|
-
add_index "#{DoubleEntry.table_name_prefix}line_metadata",
|
36
|
-
["line_id", "key", "value"],
|
37
|
-
:name => "lines_meta_line_id_key_value_idx"
|
38
|
-
end
|
15
|
+
gem.metadata = {
|
16
|
+
'bug_tracker_uri' => 'https://github.com/envato/double_entry/issues',
|
17
|
+
'changelog_uri' => 'https://github.com/envato/double_entry/blob/master/CHANGELOG.md',
|
18
|
+
'documentation_uri' => 'https://www.rubydoc.info/github/envato/double_entry/',
|
19
|
+
'source_code_uri' => 'https://github.com/envato/double_entry',
|
20
|
+
}
|
39
21
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
Please ensure that you update your database accordingly.
|
46
|
-
POSTINSTALLMESSAGE
|
22
|
+
gem.files = `git ls-files -z`.split("\x0").select do |f|
|
23
|
+
f.match(%r{^(?:double_entry.gemspec|README|LICENSE|CHANGELOG|lib/)})
|
24
|
+
end
|
25
|
+
gem.require_paths = ['lib']
|
26
|
+
gem.required_ruby_version = '>= 2.2.0'
|
47
27
|
|
48
|
-
gem.add_dependency 'money', '>= 6.0.0'
|
49
28
|
gem.add_dependency 'activerecord', '>= 3.2.0'
|
50
29
|
gem.add_dependency 'activesupport', '>= 3.2.0'
|
30
|
+
gem.add_dependency 'money', '>= 6.0.0'
|
51
31
|
gem.add_dependency 'railties', '>= 3.2.0'
|
52
32
|
|
53
|
-
gem.add_development_dependency 'rake'
|
54
33
|
gem.add_development_dependency 'mysql2'
|
55
34
|
gem.add_development_dependency 'pg'
|
35
|
+
gem.add_development_dependency 'rake'
|
56
36
|
gem.add_development_dependency 'sqlite3'
|
57
37
|
|
58
|
-
gem.add_development_dependency 'rspec'
|
59
|
-
gem.add_development_dependency 'rspec-its'
|
60
|
-
gem.add_development_dependency 'rspec-instafail'
|
61
38
|
gem.add_development_dependency 'database_cleaner'
|
39
|
+
gem.add_development_dependency 'factory_bot'
|
62
40
|
gem.add_development_dependency 'generator_spec'
|
63
|
-
gem.add_development_dependency '
|
64
|
-
gem.add_development_dependency '
|
65
|
-
gem.add_development_dependency 'test-unit'
|
66
|
-
|
67
|
-
gem.add_development_dependency 'pry'
|
68
|
-
gem.add_development_dependency 'pry-doc'
|
69
|
-
gem.add_development_dependency 'pry-byebug' if RUBY_VERSION >= '2.0.0'
|
70
|
-
gem.add_development_dependency 'pry-stack_explorer'
|
71
|
-
gem.add_development_dependency 'awesome_print'
|
41
|
+
gem.add_development_dependency 'rspec'
|
42
|
+
gem.add_development_dependency 'rspec-its'
|
72
43
|
gem.add_development_dependency 'ruby-prof'
|
44
|
+
gem.add_development_dependency 'timecop'
|
73
45
|
end
|
@@ -18,7 +18,7 @@ module ActiveRecord
|
|
18
18
|
yield
|
19
19
|
rescue ActiveRecord::StatementInvalid => exception
|
20
20
|
if exception.message =~ /deadlock/i || exception.message =~ /database is locked/i
|
21
|
-
ActiveSupport::Notifications.publish('deadlock_restart.
|
21
|
+
ActiveSupport::Notifications.publish('deadlock_restart.double_entry', :exception => exception)
|
22
22
|
|
23
23
|
raise ActiveRecord::RestartTransaction
|
24
24
|
else
|
@@ -46,7 +46,7 @@ module ActiveRecord
|
|
46
46
|
yield
|
47
47
|
rescue ActiveRecord::StatementInvalid, ActiveRecord::RecordNotUnique => exception
|
48
48
|
if exception.message =~ /duplicate/i || exception.message =~ /ConstraintException/
|
49
|
-
ActiveSupport::Notifications.publish('duplicate_ignore.
|
49
|
+
ActiveSupport::Notifications.publish('duplicate_ignore.double_entry', :exception => exception)
|
50
50
|
|
51
51
|
# Just ignore it...someone else has already created the record.
|
52
52
|
else
|
@@ -63,7 +63,7 @@ module ActiveRecord
|
|
63
63
|
if exception.message =~ /deadlock/i || exception.message =~ /database is locked/i
|
64
64
|
# Somebody else is in the midst of creating the record. We'd better
|
65
65
|
# retry, so we ensure they're done before we move on.
|
66
|
-
ActiveSupport::Notifications.publish('deadlock_retry.
|
66
|
+
ActiveSupport::Notifications.publish('deadlock_retry.double_entry', :exception => exception)
|
67
67
|
|
68
68
|
retry
|
69
69
|
else
|
data/lib/double_entry/account.rb
CHANGED
@@ -1,79 +1,67 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require 'forwardable'
|
3
|
+
|
2
4
|
module DoubleEntry
|
3
5
|
class Account
|
4
6
|
class << self
|
5
|
-
|
7
|
+
attr_accessor :scope_identifier_max_length, :account_identifier_max_length
|
8
|
+
attr_writer :accounts
|
6
9
|
|
7
10
|
# @api private
|
8
11
|
def accounts
|
9
12
|
@accounts ||= Set.new
|
10
13
|
end
|
11
14
|
|
12
|
-
# @api private
|
13
|
-
def scope_identifier_max_length
|
14
|
-
@scope_identifier_max_length ||= 23
|
15
|
-
end
|
16
|
-
|
17
|
-
# @api private
|
18
|
-
def account_identifier_max_length
|
19
|
-
@account_identifier_max_length ||= 31
|
20
|
-
end
|
21
|
-
|
22
15
|
# @api private
|
23
16
|
def account(identifier, options = {})
|
24
|
-
account = accounts.find(identifier, options[:scope].present?)
|
25
|
-
Instance.new(:account => account, :scope => options[:scope])
|
17
|
+
account = accounts.find(identifier, (options[:scope].present? || options[:scope_identity].present?))
|
18
|
+
Instance.new(:account => account, :scope => options[:scope], :scope_identity => options[:scope_identity])
|
26
19
|
end
|
27
20
|
|
28
21
|
# @api private
|
29
22
|
def currency(identifier)
|
30
|
-
accounts.
|
23
|
+
accounts.find_without_scope(identifier).try(:currency)
|
31
24
|
end
|
32
25
|
end
|
33
26
|
|
34
27
|
# @api private
|
35
|
-
class Set
|
28
|
+
class Set
|
29
|
+
extend Forwardable
|
30
|
+
|
31
|
+
delegate [:each, :map] => :all
|
32
|
+
|
36
33
|
def define(attributes)
|
37
|
-
|
34
|
+
Account.new(attributes).tap do |account|
|
35
|
+
if find_without_scope(account.identifier)
|
36
|
+
fail DuplicateAccount
|
37
|
+
else
|
38
|
+
backing_collection[account.identifier] = account
|
39
|
+
end
|
40
|
+
end
|
38
41
|
end
|
39
42
|
|
40
43
|
def find(identifier, scoped)
|
41
|
-
found_account =
|
42
|
-
account.identifier == identifier && account.scoped? == scoped
|
43
|
-
end
|
44
|
-
fail UnknownAccount, "account: #{identifier} scoped?: #{scoped}" unless found_account
|
45
|
-
found_account
|
46
|
-
end
|
44
|
+
found_account = find_without_scope(identifier)
|
47
45
|
|
48
|
-
|
49
|
-
|
50
|
-
fail DuplicateAccount
|
46
|
+
if found_account && found_account.scoped? == scoped
|
47
|
+
found_account
|
51
48
|
else
|
52
|
-
|
49
|
+
fail UnknownAccount, "account: #{identifier} scoped?: #{scoped}"
|
53
50
|
end
|
54
51
|
end
|
55
52
|
|
56
|
-
def
|
57
|
-
|
53
|
+
def find_without_scope(identifier)
|
54
|
+
backing_collection[identifier]
|
58
55
|
end
|
59
|
-
end
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
@active_record_class = active_record_class
|
57
|
+
def all
|
58
|
+
backing_collection.values
|
64
59
|
end
|
65
60
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
value.id
|
71
|
-
when String, Fixnum
|
72
|
-
value
|
73
|
-
else
|
74
|
-
fail AccountScopeMismatchError, "Expected instance of `#{@active_record_class}`, received instance of `#{value.class}`"
|
75
|
-
end
|
76
|
-
end
|
61
|
+
private
|
62
|
+
|
63
|
+
def backing_collection
|
64
|
+
@backing_collection ||= Hash.new
|
77
65
|
end
|
78
66
|
end
|
79
67
|
|
@@ -84,10 +72,15 @@ module DoubleEntry
|
|
84
72
|
def initialize(args)
|
85
73
|
@account = args[:account]
|
86
74
|
@scope = args[:scope]
|
75
|
+
@scope_identity = args[:scope_identity]
|
87
76
|
ensure_scope_is_valid
|
88
77
|
end
|
89
78
|
|
90
79
|
def scope_identity
|
80
|
+
@scope_identity || call_scope_identifier
|
81
|
+
end
|
82
|
+
|
83
|
+
def call_scope_identifier
|
91
84
|
scope_identifier.call(scope).to_s if scoped?
|
92
85
|
end
|
93
86
|
|
@@ -138,7 +131,7 @@ module DoubleEntry
|
|
138
131
|
|
139
132
|
def ensure_scope_is_valid
|
140
133
|
identity = scope_identity
|
141
|
-
if identity && identity.length > Account.scope_identifier_max_length
|
134
|
+
if identity && Account.scope_identifier_max_length && identity.length > Account.scope_identifier_max_length
|
142
135
|
fail ScopeIdentifierTooLongError,
|
143
136
|
"scope identifier '#{identity}' is too long. Please limit it to #{Account.scope_identifier_max_length} characters."
|
144
137
|
end
|
@@ -153,7 +146,7 @@ module DoubleEntry
|
|
153
146
|
@positive_only = args[:positive_only]
|
154
147
|
@negative_only = args[:negative_only]
|
155
148
|
@currency = args[:currency] || Money.default_currency
|
156
|
-
if identifier.length > Account.account_identifier_max_length
|
149
|
+
if Account.account_identifier_max_length && identifier.length > Account.account_identifier_max_length
|
157
150
|
fail AccountIdentifierTooLongError,
|
158
151
|
"account identifier '#{identifier}' is too long. Please limit it to #{Account.account_identifier_max_length} characters."
|
159
152
|
end
|
@@ -25,7 +25,7 @@ module DoubleEntry
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def account
|
28
|
-
DoubleEntry.account(self[:account].to_sym, :
|
28
|
+
DoubleEntry.account(self[:account].to_sym, :scope_identity => self[:scope])
|
29
29
|
end
|
30
30
|
|
31
31
|
def self.find_by_account(account, options = {})
|
@@ -33,5 +33,22 @@ module DoubleEntry
|
|
33
33
|
scope = scope.lock(true) if options[:lock]
|
34
34
|
scope.first
|
35
35
|
end
|
36
|
+
|
37
|
+
# Identify the scopes with the given account identifier holding at least
|
38
|
+
# the provided minimum balance.
|
39
|
+
#
|
40
|
+
# @example Find users with at least $1,000,000 in their savings accounts
|
41
|
+
# DoubleEntry::AccountBalance.scopes_with_minimum_balance_for_account(
|
42
|
+
# 1_000_000.dollars,
|
43
|
+
# :savings,
|
44
|
+
# ) # might return the user ids: [ '1423', '12232', '34729' ]
|
45
|
+
# @param [Money] minimum_balance Minimum account balance a scope must have
|
46
|
+
# to be included in the result set.
|
47
|
+
# @param [Symbol] account_identifier
|
48
|
+
# @return [Array<String>] Scopes
|
49
|
+
#
|
50
|
+
def self.scopes_with_minimum_balance_for_account(minimum_balance, account_identifier)
|
51
|
+
where(account: account_identifier).where('balance >= ?', minimum_balance.fractional).pluck(:scope)
|
52
|
+
end
|
36
53
|
end
|
37
54
|
end
|
data/lib/double_entry/errors.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module DoubleEntry
|
3
|
-
class
|
4
|
-
class
|
5
|
-
class
|
6
|
-
class
|
7
|
-
class
|
8
|
-
class
|
9
|
-
class
|
10
|
-
class
|
11
|
-
class
|
12
|
-
class
|
13
|
-
class
|
14
|
-
class
|
15
|
-
class
|
3
|
+
class DoubleEntryError < RuntimeError; end
|
4
|
+
class UnknownAccount < DoubleEntryError; end
|
5
|
+
class AccountIdentifierTooLongError < DoubleEntryError; end
|
6
|
+
class ScopeIdentifierTooLongError < DoubleEntryError; end
|
7
|
+
class TransferNotAllowed < DoubleEntryError; end
|
8
|
+
class TransferIsNegative < DoubleEntryError; end
|
9
|
+
class TransferCodeTooLongError < DoubleEntryError; end
|
10
|
+
class DuplicateAccount < DoubleEntryError; end
|
11
|
+
class DuplicateTransfer < DoubleEntryError; end
|
12
|
+
class AccountWouldBeSentNegative < DoubleEntryError; end
|
13
|
+
class AccountWouldBeSentPositiveError < DoubleEntryError; end
|
14
|
+
class MismatchedCurrencies < DoubleEntryError; end
|
15
|
+
class MissingAccountError < DoubleEntryError; end
|
16
16
|
end
|
data/lib/double_entry/line.rb
CHANGED
@@ -57,6 +57,7 @@ module DoubleEntry
|
|
57
57
|
class Line < ActiveRecord::Base
|
58
58
|
belongs_to :detail, :polymorphic => true
|
59
59
|
has_many :metadata, :class_name => 'DoubleEntry::LineMetadata'
|
60
|
+
scope :with_id_greater_than, ->(id) { where('id > ?', id) }
|
60
61
|
|
61
62
|
def amount
|
62
63
|
self[:amount] && Money.new(self[:amount], currency)
|
@@ -101,7 +102,7 @@ module DoubleEntry
|
|
101
102
|
end
|
102
103
|
|
103
104
|
def account
|
104
|
-
DoubleEntry.account(self[:account].to_sym, :
|
105
|
+
DoubleEntry.account(self[:account].to_sym, :scope_identity => scope)
|
105
106
|
end
|
106
107
|
|
107
108
|
def currency
|
@@ -116,7 +117,7 @@ module DoubleEntry
|
|
116
117
|
end
|
117
118
|
|
118
119
|
def partner_account
|
119
|
-
DoubleEntry.account(self[:partner_account].to_sym, :
|
120
|
+
DoubleEntry.account(self[:partner_account].to_sym, :scope_identity => partner_scope)
|
120
121
|
end
|
121
122
|
|
122
123
|
def partner
|
@@ -9,6 +9,7 @@ require 'double_entry/reporting/month_range'
|
|
9
9
|
require 'double_entry/reporting/year_range'
|
10
10
|
require 'double_entry/reporting/line_aggregate'
|
11
11
|
require 'double_entry/reporting/line_aggregate_filter'
|
12
|
+
require 'double_entry/reporting/line_metadata_filter'
|
12
13
|
require 'double_entry/reporting/time_range_array'
|
13
14
|
|
14
15
|
module DoubleEntry
|
@@ -62,9 +63,12 @@ module DoubleEntry
|
|
62
63
|
# @param [Symbol] code The application specific code for the type of
|
63
64
|
# transfer to perform an aggregate calculation on. As specified in the
|
64
65
|
# transfer configuration.
|
65
|
-
# @param [DoubleEntry::Reporting::TimeRange] Only include transfers in
|
66
|
-
# given time range in the calculation.
|
67
|
-
# @
|
66
|
+
# @param [DoubleEntry::Reporting::TimeRange] range Only include transfers in
|
67
|
+
# the given time range in the calculation.
|
68
|
+
# @param [Symbol] partner_account The symbol identifying the partner account
|
69
|
+
# to perform the aggregate calculatoin on. As specified in the account
|
70
|
+
# configuration.
|
71
|
+
# @param [Array<Hash<Symbol,Hash<Symbol,Object>>>] filter
|
68
72
|
# An array of custom filter to apply before performing the aggregate
|
69
73
|
# calculation. Filters can be either scope filters, where the name must be
|
70
74
|
# specified, or they can be metadata filters, where the key/value pair to
|
@@ -73,13 +77,14 @@ module DoubleEntry
|
|
73
77
|
# class, as the example above shows. Scope filters may also take a list of
|
74
78
|
# arguments to pass into the monkey patched scope, and, if provided, must
|
75
79
|
# be contained within an array.
|
76
|
-
# @return [Money,
|
77
|
-
# calculations, or a
|
80
|
+
# @return [Money, Integer] Returns a Money object for :sum and :average
|
81
|
+
# calculations, or a Integer for :count calculations.
|
78
82
|
# @raise [Reporting::AggregateFunctionNotSupported] The provided function
|
79
83
|
# is not supported.
|
80
84
|
#
|
81
|
-
def aggregate(function
|
82
|
-
Aggregate.formatted_amount(function, account, code, range,
|
85
|
+
def aggregate(function:, account:, code:, range:, partner_account: nil, filter: nil)
|
86
|
+
Aggregate.formatted_amount(function: function, account: account, code: code, range: range,
|
87
|
+
partner_account: partner_account, filter: filter)
|
83
88
|
end
|
84
89
|
|
85
90
|
# Perform an aggregate calculation on a set of transfers for an account
|
@@ -104,54 +109,37 @@ module DoubleEntry
|
|
104
109
|
# @param [Symbol] code The application specific code for the type of
|
105
110
|
# transfer to perform an aggregate calculation on. As specified in the
|
106
111
|
# transfer configuration.
|
107
|
-
# @
|
112
|
+
# @param [Symbol] partner_account The symbol identifying the partner account
|
113
|
+
# to perform the aggregative calculation on. As specified in the account
|
114
|
+
# configuration.
|
115
|
+
# @param [Array<Symbol>, Array<Hash<Symbol, Object>>] filter
|
108
116
|
# A custom filter to apply before performing the aggregate calculation.
|
109
117
|
# Currently, filters must be monkey patched as scopes into the
|
110
118
|
# DoubleEntry::Line class in order to be used as filters, as the example
|
111
119
|
# shows. If the filter requires a parameter, it must be given in a Hash,
|
112
120
|
# otherwise pass an array with the symbol names for the defined scopes.
|
113
|
-
# @
|
114
|
-
# for.
|
121
|
+
# @param [String] range_type The type of time range to return data
|
122
|
+
# for. For example, specifying 'month' will return an array of the resulting
|
115
123
|
# aggregate calculation for each month.
|
116
124
|
# Valid range_types are 'hour', 'day', 'week', 'month', and 'year'
|
117
|
-
# @
|
125
|
+
# @param [String] start The start date for the time range to perform
|
118
126
|
# calculations in. The default start date is the start_of_business (can
|
119
127
|
# be specified in configuration).
|
120
128
|
# The format of the string must be as follows: 'YYYY-mm-dd'
|
121
|
-
# @
|
129
|
+
# @param [String] finish The finish (or end) date for the time range
|
122
130
|
# to perform calculations in. The default finish date is the current date.
|
123
131
|
# The format of the string must be as follows: 'YYYY-mm-dd'
|
124
|
-
# @return [Array<Money,
|
125
|
-
# and :average calculations, or an array of
|
132
|
+
# @return [Array<Money, Integer>] Returns an array of Money objects for :sum
|
133
|
+
# and :average calculations, or an array of Integer for :count calculations.
|
126
134
|
# The array is indexed by the range_type. For example, if range_type is
|
127
135
|
# specified as 'month', each index in the array will represent a month.
|
128
136
|
# @raise [Reporting::AggregateFunctionNotSupported] The provided function
|
129
137
|
# is not supported.
|
130
138
|
#
|
131
|
-
def aggregate_array(function
|
132
|
-
|
133
|
-
|
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
|
139
|
+
def aggregate_array(function:, account:, code:, partner_account: nil, filter: nil,
|
140
|
+
range_type: nil, start: nil, finish: nil)
|
141
|
+
AggregateArray.new(function: function, account: account, code: code, partner_account: partner_account,
|
142
|
+
filter: filter, range_type: range_type, start: start, finish: finish)
|
155
143
|
end
|
156
144
|
|
157
145
|
# This is used by the concurrency test script.
|