double_entry 1.0.1 → 2.0.0.beta5

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 (79) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +497 -0
  3. data/README.md +107 -44
  4. data/double_entry.gemspec +22 -49
  5. data/lib/active_record/locking_extensions.rb +3 -3
  6. data/lib/active_record/locking_extensions/log_subscriber.rb +1 -1
  7. data/lib/double_entry.rb +29 -21
  8. data/lib/double_entry/account.rb +39 -46
  9. data/lib/double_entry/account_balance.rb +20 -3
  10. data/lib/double_entry/balance_calculator.rb +5 -5
  11. data/lib/double_entry/configurable.rb +1 -0
  12. data/lib/double_entry/configuration.rb +8 -2
  13. data/lib/double_entry/errors.rb +13 -13
  14. data/lib/double_entry/line.rb +7 -6
  15. data/lib/double_entry/locking.rb +5 -5
  16. data/lib/double_entry/transfer.rb +37 -30
  17. data/lib/double_entry/validation.rb +1 -0
  18. data/lib/double_entry/validation/account_fixer.rb +36 -0
  19. data/lib/double_entry/validation/line_check.rb +25 -43
  20. data/lib/double_entry/version.rb +1 -1
  21. data/lib/generators/double_entry/install/install_generator.rb +22 -1
  22. data/lib/generators/double_entry/install/templates/initializer.rb +20 -0
  23. data/lib/generators/double_entry/install/templates/migration.rb +45 -55
  24. metadata +35 -256
  25. data/.gitignore +0 -32
  26. data/.rspec +0 -2
  27. data/.travis.yml +0 -29
  28. data/.yardopts +0 -2
  29. data/Gemfile +0 -2
  30. data/Rakefile +0 -15
  31. data/lib/double_entry/reporting.rb +0 -181
  32. data/lib/double_entry/reporting/aggregate.rb +0 -110
  33. data/lib/double_entry/reporting/aggregate_array.rb +0 -76
  34. data/lib/double_entry/reporting/day_range.rb +0 -42
  35. data/lib/double_entry/reporting/hour_range.rb +0 -45
  36. data/lib/double_entry/reporting/line_aggregate.rb +0 -16
  37. data/lib/double_entry/reporting/line_aggregate_filter.rb +0 -79
  38. data/lib/double_entry/reporting/month_range.rb +0 -94
  39. data/lib/double_entry/reporting/time_range.rb +0 -59
  40. data/lib/double_entry/reporting/time_range_array.rb +0 -49
  41. data/lib/double_entry/reporting/week_range.rb +0 -107
  42. data/lib/double_entry/reporting/year_range.rb +0 -40
  43. data/script/jack_hammer +0 -210
  44. data/script/setup.sh +0 -8
  45. data/spec/active_record/locking_extensions_spec.rb +0 -110
  46. data/spec/double_entry/account_balance_spec.rb +0 -7
  47. data/spec/double_entry/account_spec.rb +0 -130
  48. data/spec/double_entry/balance_calculator_spec.rb +0 -88
  49. data/spec/double_entry/configuration_spec.rb +0 -50
  50. data/spec/double_entry/line_spec.rb +0 -80
  51. data/spec/double_entry/locking_spec.rb +0 -214
  52. data/spec/double_entry/performance/double_entry_performance_spec.rb +0 -32
  53. data/spec/double_entry/performance/reporting/aggregate_performance_spec.rb +0 -50
  54. data/spec/double_entry/reporting/aggregate_array_spec.rb +0 -123
  55. data/spec/double_entry/reporting/aggregate_spec.rb +0 -205
  56. data/spec/double_entry/reporting/line_aggregate_filter_spec.rb +0 -90
  57. data/spec/double_entry/reporting/line_aggregate_spec.rb +0 -39
  58. data/spec/double_entry/reporting/month_range_spec.rb +0 -139
  59. data/spec/double_entry/reporting/time_range_array_spec.rb +0 -169
  60. data/spec/double_entry/reporting/time_range_spec.rb +0 -45
  61. data/spec/double_entry/reporting/week_range_spec.rb +0 -103
  62. data/spec/double_entry/reporting_spec.rb +0 -181
  63. data/spec/double_entry/transfer_spec.rb +0 -93
  64. data/spec/double_entry/validation/line_check_spec.rb +0 -99
  65. data/spec/double_entry_spec.rb +0 -428
  66. data/spec/generators/double_entry/install/install_generator_spec.rb +0 -30
  67. data/spec/spec_helper.rb +0 -118
  68. data/spec/support/accounts.rb +0 -21
  69. data/spec/support/blueprints.rb +0 -43
  70. data/spec/support/database.example.yml +0 -21
  71. data/spec/support/database.travis.yml +0 -24
  72. data/spec/support/double_entry_spec_helper.rb +0 -27
  73. data/spec/support/gemfiles/Gemfile.rails-3.2.x +0 -8
  74. data/spec/support/gemfiles/Gemfile.rails-4.1.x +0 -6
  75. data/spec/support/gemfiles/Gemfile.rails-4.2.x +0 -5
  76. data/spec/support/gemfiles/Gemfile.rails-5.0.x +0 -5
  77. data/spec/support/performance_helper.rb +0 -26
  78. data/spec/support/reporting_configuration.rb +0 -6
  79. data/spec/support/schema.rb +0 -74
@@ -1,42 +0,0 @@
1
- # encoding: utf-8
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
- 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
40
- end
41
- end
42
- end
@@ -1,45 +0,0 @@
1
- # encoding: utf-8
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
- 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
43
- end
44
- end
45
- end
@@ -1,16 +0,0 @@
1
- # encoding: utf-8
2
- module DoubleEntry
3
- module Reporting
4
- class LineAggregate < ActiveRecord::Base
5
- def self.aggregate(function, account, code, range, named_scopes)
6
- collection_filter = LineAggregateFilter.new(account, code, range, named_scopes)
7
- collection = collection_filter.filter
8
- collection.send(function, :amount)
9
- end
10
-
11
- def key
12
- "#{year}:#{month}:#{week}:#{day}:#{hour}"
13
- end
14
- end
15
- end
16
- end
@@ -1,79 +0,0 @@
1
- # encoding: utf-8
2
- module DoubleEntry
3
- module Reporting
4
- class LineAggregateFilter
5
- def initialize(account, code, range, filter_criteria)
6
- @account = account
7
- @code = code
8
- @range = range
9
- @filter_criteria = filter_criteria || []
10
- end
11
-
12
- def filter
13
- @collection ||= apply_filters
14
- end
15
-
16
- private
17
-
18
- def apply_filters
19
- collection = apply_filter_criteria.
20
- where(:account => @account).
21
- where(:created_at => @range.start..@range.finish)
22
- collection = collection.where(:code => @code) if @code
23
-
24
- collection
25
- end
26
-
27
- # a lot of the trickier reports will use filters defined
28
- # in filter_criteria to bring in data from other tables.
29
- # For example:
30
- #
31
- # filter_criteria = [
32
- # # an example of calling a named scope called with arguments
33
- # {
34
- # :scope => {
35
- # :name => :ten_dollar_purchases_by_category,
36
- # :arguments => [:cat_videos, :cat_pictures]
37
- # }
38
- # },
39
- # # an example of calling a named scope with no arguments
40
- # {
41
- # :scope => {
42
- # :name => :ten_dollar_purchases
43
- # }
44
- # },
45
- # # an example of providing a single metadatum criteria to filter on
46
- # {
47
- # :metadata => {
48
- # :meme => :business_cat
49
- # }
50
- # }
51
- # ]
52
- def apply_filter_criteria
53
- @filter_criteria.reduce(DoubleEntry::Line) do |collection, filter|
54
- if filter[:scope].present?
55
- filter_by_scope(collection, filter[:scope])
56
- elsif filter[:metadata].present?
57
- filter_by_metadata(collection, filter[:metadata])
58
- else
59
- collection
60
- end
61
- end
62
- end
63
-
64
- def filter_by_scope(collection, scope)
65
- collection.public_send(scope[:name], *scope[:arguments])
66
- end
67
-
68
- def filter_by_metadata(collection, metadata)
69
- metadata.reduce(collection.joins(:metadata)) do |filtered_collection, (key, value)|
70
- filtered_collection.where(metadata_table => { :key => key, :value => value })
71
- end
72
- end
73
-
74
- def metadata_table
75
- DoubleEntry::LineMetadata.table_name.to_sym
76
- end
77
- end
78
- end
79
- end
@@ -1,94 +0,0 @@
1
- # encoding: utf-8
2
- module DoubleEntry
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
9
-
10
- def current
11
- from_time(Time.now)
12
- end
13
-
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
- end
29
- end
30
-
31
- def earliest_month
32
- from_time(Reporting.configuration.start_of_business)
33
- end
34
- end
35
-
36
- attr_reader :year, :month
37
-
38
- def initialize(options = {})
39
- super options
40
-
41
- if options.present?
42
- @month = options[:month]
43
-
44
- month_start = Time.local(year, options[:month], 1)
45
- @start = month_start
46
- @finish = month_start.end_of_month
47
-
48
- @start = MonthRange.earliest_month.start if options[:range_type] == :all_time
49
- end
50
- end
51
-
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
- end
59
-
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
- end
67
-
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
73
-
74
- alias_method :succ, :next
75
-
76
- def <=>(other)
77
- start <=> other.start
78
- end
79
-
80
- def ==(other)
81
- month == other.month &&
82
- year == other.year
83
- end
84
-
85
- def all_time
86
- MonthRange.new(:year => year, :month => month, :range_type => :all_time)
87
- end
88
-
89
- def to_s
90
- start.strftime('%Y, %b')
91
- end
92
- end
93
- end
94
- end
@@ -1,59 +0,0 @@
1
- # encoding: utf-8
2
- module DoubleEntry
3
- module Reporting
4
- class TimeRange
5
- attr_reader :start, :finish
6
- attr_reader :year, :month, :week, :day, :hour, :range_type
7
-
8
- def self.make(options = {})
9
- @options = options
10
- case
11
- when options[:year] && options[:week] && options[:day] && options[:hour]
12
- HourRange.new(options)
13
- when options[:year] && options[:week] && options[:day]
14
- DayRange.new(options)
15
- when options[:year] && options[:week]
16
- WeekRange.new(options)
17
- when options[:year] && options[:month]
18
- MonthRange.new(options)
19
- when options[:year]
20
- YearRange.new(options)
21
- else
22
- fail "Invalid range information #{options}"
23
- end
24
- end
25
-
26
- def self.range_from_time_for_period(start_time, period_name)
27
- case period_name
28
- when 'month'
29
- YearRange.from_time(start_time)
30
- when 'week'
31
- YearRange.from_time(start_time)
32
- when 'day'
33
- MonthRange.from_time(start_time)
34
- when 'hour'
35
- DayRange.from_time(start_time)
36
- end
37
- end
38
-
39
- def include?(time)
40
- time >= @start &&
41
- time <= @finish
42
- end
43
-
44
- def initialize(options)
45
- @year = options[:year]
46
- @range_type = options[:range_type] || :normal
47
- @month = @week = @day = @hour = nil
48
- end
49
-
50
- def key
51
- "#{@year}:#{@month}:#{@week}:#{@day}:#{@hour}"
52
- end
53
-
54
- def human_readable_name
55
- self.class.name.gsub('DoubleEntry::Reporting::', '').gsub('Range', '')
56
- end
57
- end
58
- end
59
- end
@@ -1,49 +0,0 @@
1
- # encoding: utf-8
2
- module DoubleEntry
3
- module Reporting
4
- class TimeRangeArray
5
- attr_reader :type, :require_start
6
- alias_method :require_start?, :require_start
7
-
8
- def initialize(options = {})
9
- @type = options[:type]
10
- @require_start = options[:require_start]
11
- end
12
-
13
- def make(start = nil, finish = nil)
14
- start = start_range(start)
15
- finish = finish_range(finish)
16
- [start].tap do |array|
17
- while start != finish
18
- start = start.next
19
- array << start
20
- end
21
- end
22
- end
23
-
24
- def start_range(start = nil)
25
- fail 'Must specify start of range' if start.blank? && require_start?
26
- start_time = start ? Time.parse(start) : Reporting.configuration.start_of_business
27
- type.from_time(start_time)
28
- end
29
-
30
- def finish_range(finish = nil)
31
- finish ? type.from_time(Time.parse(finish)) : type.current
32
- end
33
-
34
- FACTORIES = {
35
- 'hour' => new(:type => HourRange, :require_start => true),
36
- 'day' => new(:type => DayRange, :require_start => true),
37
- 'week' => new(:type => WeekRange, :require_start => true),
38
- 'month' => new(:type => MonthRange, :require_start => false),
39
- 'year' => new(:type => YearRange, :require_start => false),
40
- }
41
-
42
- def self.make(range_type, start = nil, finish = nil)
43
- factory = FACTORIES[range_type]
44
- fail ArgumentError, "Invalid range type '#{range_type}'" unless factory
45
- factory.make(start, finish)
46
- end
47
- end
48
- end
49
- end
@@ -1,107 +0,0 @@
1
- # encoding: utf-8
2
- module DoubleEntry
3
- module Reporting
4
- # We use a particularly crazy week numbering system: week 1 of any given year
5
- # is the first week with any days that fall into that year.
6
- #
7
- # So, for example, week 1 of 2011 starts on 27 Dec 2010.
8
- class WeekRange < TimeRange
9
- class << self
10
- def from_time(time)
11
- date = time.to_date
12
- week = date.cweek
13
- year = date.end_of_week.year
14
-
15
- if date.beginning_of_week.year != year
16
- week = 1
17
- elsif date.beginning_of_year.cwday > Date::DAYNAMES.index('Thursday')
18
- week += 1
19
- end
20
-
21
- new(:year => year, :week => week)
22
- end
23
-
24
- def current
25
- from_time(Time.now)
26
- end
27
-
28
- # Obtain a sequence of WeekRanges from the given start to the current
29
- # week.
30
- #
31
- # @option options :from [Time] Time of the first in the returned sequence
32
- # of WeekRanges.
33
- # @return [Array<WeekRange>]
34
- def reportable_weeks(options = {})
35
- week = options[:from] ? from_time(options[:from]) : earliest_week
36
- last_in_sequence = current
37
- [week].tap do |weeks|
38
- while week != last_in_sequence
39
- week = week.next
40
- weeks << week
41
- end
42
- end
43
- end
44
-
45
- private
46
-
47
- def start_of_year(year)
48
- Time.local(year, 1, 1).beginning_of_week
49
- end
50
-
51
- def earliest_week
52
- from_time(Reporting.configuration.start_of_business)
53
- end
54
- end
55
-
56
- attr_reader :year, :week
57
-
58
- def initialize(options = {})
59
- super options
60
-
61
- if options.present?
62
- @week = options[:week]
63
-
64
- @start = week_and_year_to_time(@week, @year)
65
- @finish = @start.end_of_week
66
-
67
- @start = earliest_week.start if options[:range_type] == :all_time
68
- end
69
- end
70
-
71
- def previous
72
- from_time(@start - 1.week)
73
- end
74
-
75
- def next
76
- from_time(@start + 1.week)
77
- end
78
-
79
- def ==(other)
80
- week == other.week &&
81
- year == other.year
82
- end
83
-
84
- def all_time
85
- self.class.new(:year => year, :week => week, :range_type => :all_time)
86
- end
87
-
88
- def to_s
89
- "#{year}, Week #{week}"
90
- end
91
-
92
- private
93
-
94
- def from_time(time)
95
- self.class.from_time(time)
96
- end
97
-
98
- def earliest_week
99
- self.class.send(:earliest_week)
100
- end
101
-
102
- def week_and_year_to_time(week, year)
103
- self.class.send(:start_of_year, year) + (week - 1).weeks
104
- end
105
- end
106
- end
107
- end