double_entry 0.10.0 → 0.10.1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -1
  3. data/.rubocop.yml +55 -0
  4. data/.travis.yml +23 -12
  5. data/README.md +5 -1
  6. data/Rakefile +8 -3
  7. data/double_entry.gemspec +4 -3
  8. data/lib/active_record/locking_extensions.rb +28 -40
  9. data/lib/active_record/locking_extensions/log_subscriber.rb +4 -4
  10. data/lib/double_entry.rb +0 -2
  11. data/lib/double_entry/account.rb +13 -16
  12. data/lib/double_entry/account_balance.rb +0 -4
  13. data/lib/double_entry/balance_calculator.rb +4 -5
  14. data/lib/double_entry/configurable.rb +0 -2
  15. data/lib/double_entry/configuration.rb +2 -3
  16. data/lib/double_entry/errors.rb +2 -2
  17. data/lib/double_entry/line.rb +13 -16
  18. data/lib/double_entry/locking.rb +13 -18
  19. data/lib/double_entry/reporting.rb +2 -3
  20. data/lib/double_entry/reporting/aggregate.rb +90 -88
  21. data/lib/double_entry/reporting/aggregate_array.rb +58 -58
  22. data/lib/double_entry/reporting/day_range.rb +37 -35
  23. data/lib/double_entry/reporting/hour_range.rb +40 -37
  24. data/lib/double_entry/reporting/line_aggregate.rb +27 -28
  25. data/lib/double_entry/reporting/month_range.rb +67 -67
  26. data/lib/double_entry/reporting/time_range.rb +40 -38
  27. data/lib/double_entry/reporting/time_range_array.rb +3 -5
  28. data/lib/double_entry/reporting/week_range.rb +77 -78
  29. data/lib/double_entry/reporting/year_range.rb +27 -27
  30. data/lib/double_entry/transfer.rb +14 -15
  31. data/lib/double_entry/validation/line_check.rb +92 -86
  32. data/lib/double_entry/version.rb +1 -1
  33. data/lib/generators/double_entry/install/install_generator.rb +1 -2
  34. data/lib/generators/double_entry/install/templates/migration.rb +0 -2
  35. data/script/jack_hammer +1 -1
  36. data/spec/active_record/locking_extensions_spec.rb +45 -38
  37. data/spec/double_entry/account_balance_spec.rb +4 -5
  38. data/spec/double_entry/account_spec.rb +43 -44
  39. data/spec/double_entry/balance_calculator_spec.rb +6 -8
  40. data/spec/double_entry/configuration_spec.rb +14 -16
  41. data/spec/double_entry/line_spec.rb +25 -26
  42. data/spec/double_entry/locking_spec.rb +34 -39
  43. data/spec/double_entry/reporting/aggregate_array_spec.rb +8 -10
  44. data/spec/double_entry/reporting/aggregate_spec.rb +84 -44
  45. data/spec/double_entry/reporting/line_aggregate_spec.rb +7 -6
  46. data/spec/double_entry/reporting/month_range_spec.rb +109 -103
  47. data/spec/double_entry/reporting/time_range_array_spec.rb +145 -135
  48. data/spec/double_entry/reporting/time_range_spec.rb +36 -35
  49. data/spec/double_entry/reporting/week_range_spec.rb +82 -76
  50. data/spec/double_entry/reporting_spec.rb +9 -13
  51. data/spec/double_entry/transfer_spec.rb +13 -15
  52. data/spec/double_entry/validation/line_check_spec.rb +73 -79
  53. data/spec/double_entry_spec.rb +65 -68
  54. data/spec/generators/double_entry/install/install_generator_spec.rb +7 -10
  55. data/spec/spec_helper.rb +68 -10
  56. data/spec/support/accounts.rb +2 -4
  57. data/spec/support/double_entry_spec_helper.rb +4 -4
  58. data/spec/support/gemfiles/Gemfile.rails-3.2.x +1 -0
  59. metadata +31 -2
@@ -1,76 +1,76 @@
1
1
  # encoding: utf-8
2
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
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
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)
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
23
 
24
- retrieve_aggregates
25
- fill_in_missing_aggregates
26
- populate_self
27
- end
24
+ retrieve_aggregates
25
+ fill_in_missing_aggregates
26
+ populate_self
27
+ end
28
28
 
29
29
  private
30
30
 
31
- def populate_self
32
- all_periods.each do |period|
33
- self << @aggregates[period.key]
31
+ def populate_self
32
+ all_periods.each do |period|
33
+ self << @aggregates[period.key]
34
+ end
34
35
  end
35
- end
36
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] = Reporting.aggregate(function, account, code, :filter => filter, :range => period)
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] = Reporting.aggregate(function, account, code, :filter => filter, :range => period)
43
+ end
43
44
  end
44
45
  end
45
- end
46
46
 
47
- # get any previously calculated aggregates
48
- def retrieve_aggregates
49
- raise ArgumentError.new("Invalid range type '#{range_type}'") unless %w(year month week day hour).include? range_type
50
- @aggregates = 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
- each_with_object({}) do |hash, result|
58
- hash[result.key] = formatted_amount(result.amount)
59
- end
60
- end
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
+ @aggregates = 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
+ each_with_object({}) do |hash, result|
58
+ hash[result.key] = formatted_amount(result.amount)
59
+ end
60
+ end
61
61
 
62
- def all_periods
63
- TimeRangeArray.make(range_type, start, finish)
64
- end
62
+ def all_periods
63
+ TimeRangeArray.make(range_type, start, finish)
64
+ end
65
65
 
66
- def formatted_amount(amount)
67
- amount ||= 0
68
- if function == "count"
69
- amount
70
- else
71
- Money.new(amount, currency)
66
+ def formatted_amount(amount)
67
+ amount ||= 0
68
+ if function == 'count'
69
+ amount
70
+ else
71
+ Money.new(amount, currency)
72
+ end
72
73
  end
73
74
  end
74
75
  end
75
- end
76
76
  end
@@ -1,40 +1,42 @@
1
1
  # encoding: utf-8
2
2
  module DoubleEntry
3
- module Reporting
4
- class DayRange < TimeRange
5
- attr_reader :year, :week, :day
6
-
7
- def initialize(options)
8
- super options
9
-
10
- @week = options[:week]
11
- @day = options[:day]
12
- week_range = WeekRange.new(options)
13
-
14
- @start = week_range.start + (options[:day] - 1).days
15
- @finish = @start.end_of_day
16
- end
17
-
18
- def self.from_time(time)
19
- week_range = WeekRange.from_time(time)
20
- DayRange.new(:year => week_range.year, :week => week_range.week, :day => time.wday == 0 ? 7 : time.wday)
21
- end
22
-
23
- def previous
24
- DayRange.from_time(@start - 1.day)
25
- end
26
-
27
- def next
28
- DayRange.from_time(@start + 1.day)
29
- end
30
-
31
- def ==(other)
32
- (self.week == other.week) and (self.year == other.year) and (self.day == other.day)
33
- end
34
-
35
- def to_s
36
- start.strftime('%Y, %a %b %d')
3
+ module Reporting
4
+ class DayRange < TimeRange
5
+ attr_reader :year, :week, :day
6
+
7
+ def initialize(options)
8
+ super options
9
+
10
+ @week = options[:week]
11
+ @day = options[:day]
12
+ week_range = WeekRange.new(options)
13
+
14
+ @start = week_range.start + (options[:day] - 1).days
15
+ @finish = @start.end_of_day
16
+ end
17
+
18
+ def self.from_time(time)
19
+ week_range = WeekRange.from_time(time)
20
+ DayRange.new(:year => week_range.year, :week => week_range.week, :day => time.wday == 0 ? 7 : time.wday)
21
+ end
22
+
23
+ def previous
24
+ DayRange.from_time(@start - 1.day)
25
+ end
26
+
27
+ def next
28
+ DayRange.from_time(@start + 1.day)
29
+ end
30
+
31
+ def ==(other)
32
+ week == other.week &&
33
+ year == other.year &&
34
+ day == other.day
35
+ end
36
+
37
+ def to_s
38
+ start.strftime('%Y, %a %b %d')
39
+ end
37
40
  end
38
41
  end
39
- end
40
42
  end
@@ -1,42 +1,45 @@
1
1
  # encoding: utf-8
2
2
  module DoubleEntry
3
- module Reporting
4
- class HourRange < TimeRange
5
- attr_reader :year, :week, :day, :hour
6
-
7
- def initialize(options)
8
- super options
9
-
10
- @week = options[:week]
11
- @day = options[:day]
12
- @hour = options[:hour]
13
-
14
- day_range = DayRange.new(options)
15
-
16
- @start = day_range.start + options[:hour].hours
17
- @finish = @start.end_of_hour
18
- end
19
-
20
- def self.from_time(time)
21
- day = DayRange.from_time(time)
22
- HourRange.new :year => day.year, :week => day.week, :day => day.day, :hour => time.hour
23
- end
24
-
25
- def previous
26
- HourRange.from_time(@start - 1.hour)
27
- end
28
-
29
- def next
30
- HourRange.from_time(@start + 1.hour)
31
- end
32
-
33
- def ==(other)
34
- (self.week == other.week) and (self.year == other.year) and (self.day == other.day) and (self.hour == other.hour)
35
- end
36
-
37
- def to_s
38
- "#{start.hour}:00:00 - #{start.hour}:59:59"
3
+ module Reporting
4
+ class HourRange < TimeRange
5
+ attr_reader :year, :week, :day, :hour
6
+
7
+ def initialize(options)
8
+ super options
9
+
10
+ @week = options[:week]
11
+ @day = options[:day]
12
+ @hour = options[:hour]
13
+
14
+ day_range = DayRange.new(options)
15
+
16
+ @start = day_range.start + options[:hour].hours
17
+ @finish = @start.end_of_hour
18
+ end
19
+
20
+ def self.from_time(time)
21
+ day = DayRange.from_time(time)
22
+ HourRange.new :year => day.year, :week => day.week, :day => day.day, :hour => time.hour
23
+ end
24
+
25
+ def previous
26
+ HourRange.from_time(@start - 1.hour)
27
+ end
28
+
29
+ def next
30
+ HourRange.from_time(@start + 1.hour)
31
+ end
32
+
33
+ def ==(other)
34
+ week == other.week &&
35
+ year == other.year &&
36
+ day == other.day &&
37
+ hour == other.hour
38
+ end
39
+
40
+ def to_s
41
+ "#{start.hour}:00:00 - #{start.hour}:59:59"
42
+ end
39
43
  end
40
44
  end
41
- end
42
45
  end
@@ -1,38 +1,37 @@
1
1
  # encoding: utf-8
2
2
  module DoubleEntry
3
- module Reporting
4
- class LineAggregate < ActiveRecord::Base
5
-
6
- def self.aggregate(function, account, code, range, named_scopes)
7
- collection = aggregate_collection(named_scopes)
8
- collection = collection.where(:account => account)
9
- collection = collection.where(:created_at => range.start..range.finish)
10
- collection = collection.where(:code => code) if code
11
- collection.send(function, :amount)
12
- end
3
+ module Reporting
4
+ class LineAggregate < ActiveRecord::Base
5
+ def self.aggregate(function, account, code, range, named_scopes)
6
+ collection = aggregate_collection(named_scopes)
7
+ collection = collection.where(:account => account)
8
+ collection = collection.where(:created_at => range.start..range.finish)
9
+ collection = collection.where(:code => code) if code
10
+ collection.send(function, :amount)
11
+ end
13
12
 
14
- # a lot of the trickier reports will use filters defined
15
- # in named_scopes to bring in data from other tables.
16
- def self.aggregate_collection(named_scopes)
17
- if named_scopes
18
- collection = DoubleEntry::Line
19
- named_scopes.each do |named_scope|
20
- if named_scope.is_a?(Hash)
21
- method_name = named_scope.keys[0]
22
- collection = collection.send(method_name, named_scope[method_name])
23
- else
24
- collection = collection.send(named_scope)
13
+ # a lot of the trickier reports will use filters defined
14
+ # in named_scopes to bring in data from other tables.
15
+ def self.aggregate_collection(named_scopes)
16
+ if named_scopes
17
+ collection = DoubleEntry::Line
18
+ named_scopes.each do |named_scope|
19
+ if named_scope.is_a?(Hash)
20
+ method_name = named_scope.keys[0]
21
+ collection = collection.send(method_name, named_scope[method_name])
22
+ else
23
+ collection = collection.send(named_scope)
24
+ end
25
25
  end
26
+ collection
27
+ else
28
+ DoubleEntry::Line
26
29
  end
27
- collection
28
- else
29
- DoubleEntry::Line
30
30
  end
31
- end
32
31
 
33
- def key
34
- "#{year}:#{month}:#{week}:#{day}:#{hour}"
32
+ def key
33
+ "#{year}:#{month}:#{week}:#{day}:#{hour}"
34
+ end
35
35
  end
36
36
  end
37
- end
38
37
  end
@@ -1,94 +1,94 @@
1
1
  # encoding: utf-8
2
2
  module DoubleEntry
3
- module Reporting
4
- class MonthRange < TimeRange
5
-
6
- class << self
7
- def from_time(time)
8
- new(:year => time.year, :month => time.month)
9
- end
3
+ module Reporting
4
+ class MonthRange < TimeRange
5
+ class << self
6
+ def from_time(time)
7
+ new(:year => time.year, :month => time.month)
8
+ end
10
9
 
11
- def current
12
- from_time(Time.now)
13
- end
10
+ def current
11
+ from_time(Time.now)
12
+ end
14
13
 
15
- # Obtain a sequence of MonthRanges from the given start to the current
16
- # month.
17
- #
18
- # @option options :from [Time] Time of the first in the returned sequence
19
- # of MonthRanges.
20
- # @return [Array<MonthRange>]
21
- def reportable_months(options = {})
22
- month = options[:from] ? from_time(options[:from]) : earliest_month
23
- last = self.current
24
- [month].tap do |months|
25
- while month != last
26
- month = month.next
27
- months << month
14
+ # Obtain a sequence of MonthRanges from the given start to the current
15
+ # month.
16
+ #
17
+ # @option options :from [Time] Time of the first in the returned sequence
18
+ # of MonthRanges.
19
+ # @return [Array<MonthRange>]
20
+ def reportable_months(options = {})
21
+ month = options[:from] ? from_time(options[:from]) : earliest_month
22
+ last = current
23
+ [month].tap do |months|
24
+ while month != last
25
+ month = month.next
26
+ months << month
27
+ end
28
28
  end
29
29
  end
30
- end
31
30
 
32
- def earliest_month
33
- from_time(Reporting.configuration.start_of_business)
31
+ def earliest_month
32
+ from_time(Reporting.configuration.start_of_business)
33
+ end
34
34
  end
35
- end
36
35
 
37
- attr_reader :year, :month
36
+ attr_reader :year, :month
38
37
 
39
- def initialize(options = {})
40
- super options
38
+ def initialize(options = {})
39
+ super options
41
40
 
42
- if options.present?
43
- @month = options[:month]
41
+ if options.present?
42
+ @month = options[:month]
44
43
 
45
- month_start = Time.local(year, options[:month], 1)
46
- @start = month_start
47
- @finish = month_start.end_of_month
44
+ month_start = Time.local(year, options[:month], 1)
45
+ @start = month_start
46
+ @finish = month_start.end_of_month
48
47
 
49
- @start = MonthRange.earliest_month.start if options[:range_type] == :all_time
48
+ @start = MonthRange.earliest_month.start if options[:range_type] == :all_time
49
+ end
50
50
  end
51
- end
52
51
 
53
- def previous
54
- if month <= 1
55
- MonthRange.new :year => year - 1, :month => 12
56
- else
57
- MonthRange.new :year => year, :month => month - 1
52
+ def previous
53
+ if month <= 1
54
+ MonthRange.new :year => year - 1, :month => 12
55
+ else
56
+ MonthRange.new :year => year, :month => month - 1
57
+ end
58
58
  end
59
- end
60
59
 
61
- def next
62
- if month >= 12
63
- MonthRange.new :year => year + 1, :month => 1
64
- else
65
- MonthRange.new :year => year, :month => month + 1
60
+ def next
61
+ if month >= 12
62
+ MonthRange.new :year => year + 1, :month => 1
63
+ else
64
+ MonthRange.new :year => year, :month => month + 1
65
+ end
66
66
  end
67
- end
68
67
 
69
- def beginning_of_financial_year
70
- first_month_of_financial_year = Reporting.configuration.first_month_of_financial_year
71
- year = (month >= first_month_of_financial_year) ? @year : (@year - 1)
72
- MonthRange.new(:year => year, :month => first_month_of_financial_year)
73
- end
68
+ def beginning_of_financial_year
69
+ first_month_of_financial_year = Reporting.configuration.first_month_of_financial_year
70
+ year = (month >= first_month_of_financial_year) ? @year : (@year - 1)
71
+ MonthRange.new(:year => year, :month => first_month_of_financial_year)
72
+ end
74
73
 
75
- alias_method :succ, :next
74
+ alias_method :succ, :next
76
75
 
77
- def <=>(other)
78
- self.start <=> other.start
79
- end
76
+ def <=>(other)
77
+ start <=> other.start
78
+ end
80
79
 
81
- def ==(other)
82
- (self.month == other.month) and (self.year == other.year)
83
- end
80
+ def ==(other)
81
+ month == other.month &&
82
+ year == other.year
83
+ end
84
84
 
85
- def all_time
86
- MonthRange.new(:year => year, :month => month, :range_type => :all_time)
87
- end
85
+ def all_time
86
+ MonthRange.new(:year => year, :month => month, :range_type => :all_time)
87
+ end
88
88
 
89
- def to_s
90
- start.strftime("%Y, %b")
89
+ def to_s
90
+ start.strftime('%Y, %b')
91
+ end
91
92
  end
92
93
  end
93
- end
94
94
  end