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.
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