double_entry 0.10.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
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