hledger-forecast 2.0.0 → 3.0.0

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{test.yml → ci.yml} +18 -10
  3. data/.github/workflows/publish_ruby_gem.yml +24 -0
  4. data/.github/workflows/release.yml +12 -13
  5. data/.mise.toml +2 -0
  6. data/CHANGELOG.md +17 -0
  7. data/Gemfile +1 -0
  8. data/README.md +149 -119
  9. data/example.csv +15 -15
  10. data/example.journal +17 -18
  11. data/hledger-forecast.gemspec +20 -18
  12. data/lib/hledger_forecast/calculator.rb +7 -15
  13. data/lib/hledger_forecast/cli.rb +98 -71
  14. data/lib/hledger_forecast/comparator.rb +12 -11
  15. data/lib/hledger_forecast/forecast.rb +29 -0
  16. data/lib/hledger_forecast/formatter.rb +13 -15
  17. data/lib/hledger_forecast/generator.rb +32 -72
  18. data/lib/hledger_forecast/settings.rb +34 -47
  19. data/lib/hledger_forecast/summarizer.rb +34 -55
  20. data/lib/hledger_forecast/summarizer_formatter.rb +75 -78
  21. data/lib/hledger_forecast/transaction.rb +63 -0
  22. data/lib/hledger_forecast/transactions/default.rb +45 -72
  23. data/lib/hledger_forecast/version.rb +1 -1
  24. data/lib/hledger_forecast.rb +21 -22
  25. data/spec/calculator_spec.rb +45 -0
  26. data/spec/cli_spec.rb +19 -17
  27. data/spec/compare_spec.rb +16 -14
  28. data/spec/computed_amounts_spec.rb +7 -7
  29. data/spec/custom_spec.rb +9 -9
  30. data/spec/formatter_spec.rb +51 -0
  31. data/spec/half-yearly_spec.rb +5 -5
  32. data/spec/monthly_end_date_spec.rb +6 -6
  33. data/spec/monthly_end_date_transaction_spec.rb +10 -10
  34. data/spec/monthly_spec.rb +7 -7
  35. data/spec/once_spec.rb +5 -5
  36. data/spec/quarterly_spec.rb +5 -5
  37. data/spec/settings_spec.rb +101 -0
  38. data/spec/stubs/forecast.csv +4 -4
  39. data/spec/summarizer_spec.rb +28 -33
  40. data/spec/tags_spec.rb +92 -0
  41. data/spec/verbose_output_spec.rb +8 -8
  42. data/spec/yearly_spec.rb +5 -5
  43. metadata +49 -13
  44. data/lib/hledger_forecast/transactions/modifiers.rb +0 -90
  45. data/lib/hledger_forecast/transactions/trackers.rb +0 -88
  46. data/lib/hledger_forecast/utilities.rb +0 -14
  47. data/spec/track_spec.rb +0 -105
@@ -1,30 +1,32 @@
1
- lib = File.expand_path('lib', __dir__)
1
+ lib = File.expand_path("lib", __dir__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
- require 'hledger_forecast/version'
4
+ require "hledger_forecast/version"
5
5
 
6
6
  Gem::Specification.new do |s|
7
- s.required_ruby_version = '~> 3.0'
8
- s.name = 'hledger-forecast'
9
- s.version = HledgerForecast::VERSION
10
- s.authors = ['Oli Morris']
11
- s.summary = "An extended wrapper around hledger's forecasting functionality"
12
- s.description = 'Use a CSV file for improved forecasting with hledger'
13
- s.email = 'olimorris@users.noreply.github.com'
14
- s.homepage = 'https://github.com/olimorris/hledger-forecast'
15
- s.license = 'MIT'
7
+ s.required_ruby_version = "~> 3.3"
8
+ s.name = "hledger-forecast"
9
+ s.version = HledgerForecast::VERSION
10
+ s.authors = ["Oli Morris"]
11
+ s.summary = "An extended wrapper around hledger's forecasting functionality"
12
+ s.description = "Use a CSV file for improved forecasting with hledger"
13
+ s.email = "olimorris@users.noreply.github.com"
14
+ s.homepage = "https://github.com/olimorris/hledger-forecast"
15
+ s.license = "MIT"
16
16
 
17
- s.add_dependency "colorize", "~> 0.8.1"
18
- s.add_dependency "dentaku", "~> 3.5.1"
19
- s.add_dependency "highline", "~> 2.1.0"
20
- s.add_dependency "money", "~> 6.16.0"
21
- s.add_dependency "terminal-table", "~> 3.0.2"
22
- s.add_development_dependency 'rspec', '~> 3.12'
17
+ s.add_dependency("abbrev", "~> 0.1")
18
+ s.add_dependency("csv", "~> 3.0")
19
+ s.add_dependency("colorize", "~> 0.8.1")
20
+ s.add_dependency("dentaku", "~> 3.5.1")
21
+ s.add_dependency("highline", "~> 2.1.0")
22
+ s.add_dependency("money", "~> 6.16.0")
23
+ s.add_dependency("terminal-table", "~> 3.0.2")
24
+ s.add_development_dependency("rspec", "~> 3.12")
23
25
 
24
26
  s.files = `git ls-files`.split("\n")
25
27
  s.test_files = `git ls-files -- spec/*`.split("\n")
26
28
  s.executables = `git ls-files -- bin/*`.split("\n").map do |f|
27
29
  File.basename(f)
28
30
  end
29
- s.require_paths = ['lib']
31
+ s.require_paths = ["lib"]
30
32
  end
@@ -1,25 +1,17 @@
1
1
  module HledgerForecast
2
- # Calculate various
3
- class Calculator
4
- def initialize
5
- @calculator = Dentaku::Calculator.new
6
- end
2
+ module Calculator
3
+ @calc = Dentaku::Calculator.new
7
4
 
8
- def evaluate(amount)
5
+ def self.evaluate(amount)
9
6
  return amount.to_f unless amount.is_a?(String)
10
7
 
11
- @calculator.evaluate(amount.slice(1..-1))
8
+ @calc.evaluate(amount.slice(1..-1))
12
9
  end
13
10
 
14
- def evaluate_date(from, to)
15
- if to[0] != "="
16
- return to if to.is_a?(Date)
17
-
18
- return Date.parse(to)
19
- end
11
+ def self.evaluate_date(from, to)
12
+ return Date.parse(to) unless to.start_with?("=")
20
13
 
21
- # Subtract a day from the final date
22
- (from >> @calculator.evaluate(to.slice(1..-1))) - 1
14
+ (from >> @calc.evaluate(to.slice(1..-1))) - 1
23
15
  end
24
16
  end
25
17
  end
@@ -4,14 +4,14 @@ module HledgerForecast
4
4
  class Cli
5
5
  def self.run(command, options)
6
6
  case command
7
- when 'generate'
7
+ when "generate"
8
8
  generate(options)
9
- when 'summarize'
9
+ when "summarize"
10
10
  summarize(options)
11
- when 'compare'
11
+ when "compare"
12
12
  compare(options)
13
13
  else
14
- puts "Unknown command: #{command}"
14
+ puts("Unknown command: #{command}")
15
15
  exit(1)
16
16
  end
17
17
  end
@@ -22,49 +22,49 @@ module HledgerForecast
22
22
 
23
23
  global = OptionParser.new do |opts|
24
24
  opts.banner = "Usage: hledger-forecast [command] [options]"
25
- opts.separator ""
26
- opts.separator "Commands:"
27
- opts.separator " generate Generate a forecast from a file"
28
- opts.separator " summarize Summarize the forecast file and output to the terminal"
29
- opts.separator " compare Compare and highlight the differences between two CSV files"
30
- opts.separator ""
31
- opts.separator "Options:"
25
+ opts.separator("")
26
+ opts.separator("Commands:")
27
+ opts.separator(" generate Generate a forecast from a file")
28
+ opts.separator(" summarize Summarize the forecast file and output to the terminal")
29
+ opts.separator(" compare Compare and highlight the differences between two CSV files")
30
+ opts.separator("")
31
+ opts.separator("Options:")
32
32
 
33
33
  opts.on_tail("-h", "--help", "Show this help message") do
34
- puts opts
34
+ puts(opts)
35
35
  exit
36
36
  end
37
37
 
38
38
  opts.on_tail("-v", "--version", "Show the installed version") do
39
- puts VERSION
39
+ puts(VERSION)
40
40
  exit
41
41
  end
42
42
  end
43
43
 
44
44
  if args.empty?
45
- puts global
45
+ puts(global)
46
46
  exit(1)
47
47
  end
48
48
 
49
49
  begin
50
50
  global.order!(args)
51
- command = args.shift || 'generate'
51
+ command = args.shift || "generate"
52
52
  rescue OptionParser::InvalidOption => e
53
- puts e
54
- puts global
53
+ puts(e)
54
+ puts(global)
55
55
  exit(1)
56
56
  end
57
57
 
58
58
  case command
59
- when 'generate'
59
+ when "generate"
60
60
  options = parse_generate_options(args)
61
- when 'summarize'
61
+ when "summarize"
62
62
  options = parse_summarize_options(args)
63
- when 'compare'
63
+ when "compare"
64
64
  options = parse_compare_options(args)
65
65
  else
66
- puts "Unknown command: #{command}"
67
- puts global
66
+ puts("Unknown command: #{command}")
67
+ puts(global)
68
68
  exit(1)
69
69
  end
70
70
 
@@ -76,41 +76,50 @@ module HledgerForecast
76
76
 
77
77
  global = OptionParser.new do |opts|
78
78
  opts.banner = "Usage: hledger-forecast generate [options]"
79
- opts.separator ""
79
+ opts.separator("")
80
80
 
81
- opts.on("-f", "--forecast FILE",
82
- "The path to the FORECAST csv file to generate from") do |file|
81
+ opts.on(
82
+ "-f",
83
+ "--forecast FILE",
84
+ "The path to the FORECAST csv file to generate from"
85
+ ) do |file|
83
86
  options[:forecast_file] = file
84
- options[:output_file] ||= file.sub('csv', 'journal')
87
+ options[:output_file] ||= file.sub("csv", "journal")
85
88
  end
86
89
 
87
- opts.on("-o", "--output-file FILE",
88
- "The path to the OUTPUT file to create") do |file|
90
+ opts.on(
91
+ "-o",
92
+ "--output-file FILE",
93
+ "The path to the OUTPUT file to create"
94
+ ) do |file|
89
95
  options[:output_file] = file
90
96
  end
91
97
 
92
- opts.on("-t", "--transaction FILE",
93
- "The path to the TRANSACTION journal file") do |file|
94
- options[:transaction_file] = file
95
- end
96
-
97
- opts.on("-v", "--verbose",
98
- "Do not group transactions in the output file") do
98
+ opts.on(
99
+ "-v",
100
+ "--verbose",
101
+ "Do not group transactions in the output file"
102
+ ) do
99
103
  options[:verbose] = true
100
104
  end
101
105
 
102
- opts.on("--force",
103
- "Force an overwrite of the output file") do
104
- options[:force] = true
106
+ opts.on(
107
+ "-t",
108
+ "--tags TAGS",
109
+ "Only include transactions with given tags (comma-separated)"
110
+ ) do |tags|
111
+ options[:tags] = tags.split(",").map(&:strip)
105
112
  end
106
113
 
107
- opts.on("--no-track",
108
- "Do not track any transactions") do
109
- options[:no_track] = true
114
+ opts.on(
115
+ "--force",
116
+ "Force an overwrite of the output file"
117
+ ) do
118
+ options[:force] = true
110
119
  end
111
120
 
112
121
  opts.on_tail("-h", "--help", "Show this help message") do
113
- puts opts
122
+ puts(opts)
114
123
  exit
115
124
  end
116
125
  end
@@ -118,13 +127,13 @@ module HledgerForecast
118
127
  begin
119
128
  global.parse!(args)
120
129
  rescue OptionParser::InvalidOption => e
121
- puts e
122
- puts global
130
+ puts(e)
131
+ puts(global)
123
132
  exit(1)
124
133
  end
125
134
 
126
135
  if options.empty?
127
- puts global
136
+ puts(global)
128
137
  exit(1)
129
138
  end
130
139
 
@@ -140,21 +149,39 @@ module HledgerForecast
140
149
 
141
150
  global = OptionParser.new do |opts|
142
151
  opts.banner = "Usage: hledger-forecast summarize [options]"
143
- opts.separator ""
152
+ opts.separator("")
144
153
 
145
- opts.on("-f", "--forecast FILE",
146
- "The path to the FORECAST csv file to summarize") do |file|
154
+ opts.on(
155
+ "-f",
156
+ "--forecast FILE",
157
+ "The path to the FORECAST csv file to summarize"
158
+ ) do |file|
147
159
  options[:forecast_file] = file
148
160
  end
149
161
 
150
- opts.on("-r", "--roll-up PERIOD",
151
- "The period to roll-up your forecasts into. One of:",
152
- "[yearly], [half-yearly], [quarterly], [monthly], [weekly], [daily]") do |rollup|
153
- options[:roll_up] = rollup
162
+ opts
163
+ .on(
164
+ "-r",
165
+ "--roll-up PERIOD",
166
+ "The period to roll-up your forecasts into. One of:",
167
+ "[yearly], [half-yearly], [quarterly], [monthly], [weekly], [daily]"
168
+ ) do |rollup|
169
+ options[:roll_up] = rollup
170
+ end
171
+
172
+ opts.on(
173
+ "-t",
174
+ "--tags TAGS",
175
+ "Only include transactions with given tags (comma-separated)"
176
+ ) do |tags|
177
+ options[:tags] = tags.split(",").map(&:strip)
154
178
  end
155
179
 
156
- opts.on("-v", "--verbose",
157
- "Show additional information in the summary") do |_|
180
+ opts.on(
181
+ "-v",
182
+ "--verbose",
183
+ "Show additional information in the summary"
184
+ ) do |_|
158
185
  options[:verbose] = true
159
186
  end
160
187
 
@@ -176,7 +203,7 @@ module HledgerForecast
176
203
  # end
177
204
 
178
205
  opts.on_tail("-h", "--help", "Show this help message") do
179
- puts opts
206
+ puts(opts)
180
207
  exit
181
208
  end
182
209
  end
@@ -184,13 +211,13 @@ module HledgerForecast
184
211
  begin
185
212
  global.parse!(args)
186
213
  rescue OptionParser::InvalidOption => e
187
- puts e
188
- puts global
214
+ puts(e)
215
+ puts(global)
189
216
  exit(1)
190
217
  end
191
218
 
192
219
  if options.empty?
193
- puts global
220
+ puts(global)
194
221
  exit(1)
195
222
  end
196
223
 
@@ -202,19 +229,19 @@ module HledgerForecast
202
229
 
203
230
  global = OptionParser.new do |opts|
204
231
  opts.banner = "Usage: hledger-forecast compare [path/to/file1.csv] [path/to/file2.csv]"
205
- opts.separator ""
232
+ opts.separator("")
206
233
  end
207
234
 
208
235
  begin
209
236
  global.parse!(args)
210
237
  rescue OptionParser::InvalidOption => e
211
- puts e
212
- puts global
238
+ puts(e)
239
+ puts(global)
213
240
  exit(1)
214
241
  end
215
242
 
216
243
  if args[0].nil? || args[1].nil?
217
- puts global
244
+ puts(global)
218
245
  exit(1)
219
246
  end
220
247
 
@@ -230,25 +257,25 @@ module HledgerForecast
230
257
  begin
231
258
  transactions = Generator.generate(forecast, options)
232
259
  rescue StandardError => e
233
- puts "An error occurred while generating transactions: #{e.message}"
260
+ puts("An error occurred while generating transactions: #{e.message}")
234
261
  exit(1)
235
262
  end
236
263
 
237
264
  output_file = options[:output_file]
238
265
 
239
266
  if File.exist?(output_file) && !options[:force]
240
- print "\nFile '#{output_file}' already exists. Overwrite? (y/n): "
267
+ print("\nFile '#{output_file}' already exists. Overwrite? (y/n): ")
241
268
  overwrite = gets.chomp.downcase
242
269
 
243
- if overwrite == 'y'
270
+ if overwrite == "y"
244
271
  File.write(output_file, transactions)
245
- puts "\nSuccess: ".bold.green + "File '#{output_file}' has been overwritten."
272
+ puts("\nSuccess: ".bold.green + "File '#{output_file}' has been overwritten.")
246
273
  else
247
- puts "\nInfo: ".bold.blue + "Operation aborted. File '#{output_file}' was not overwritten."
274
+ puts("\nInfo: ".bold.blue + "Operation aborted. File '#{output_file}' was not overwritten.")
248
275
  end
249
276
  else
250
277
  File.write(output_file, transactions)
251
- puts "\nSuccess: ".bold.green + "File '#{output_file}' has been created"
278
+ puts("\nSuccess: ".bold.green + "File '#{output_file}' has been created")
252
279
  end
253
280
  end
254
281
 
@@ -258,15 +285,15 @@ module HledgerForecast
258
285
 
259
286
  summarizer = Summarizer.summarize(config, options)
260
287
 
261
- puts SummarizerFormatter.format(summarizer[:output], summarizer[:settings])
288
+ puts(SummarizerFormatter.format(summarizer[:output], summarizer[:settings]))
262
289
  end
263
290
 
264
291
  def self.compare(options)
265
292
  if !File.exist?(options[:file1]) || !File.exist?(options[:file2])
266
- return puts "\nError: ".bold.red + "One or more of the files could not be found to compare"
293
+ return puts("\nError: ".bold.red + "One or more of the files could not be found to compare")
267
294
  end
268
295
 
269
- puts Comparator.compare(options[:file1], options[:file2])
296
+ puts(Comparator.compare(options[:file1], options[:file2]))
270
297
  end
271
298
  end
272
299
  end
@@ -20,17 +20,17 @@ module HledgerForecast
20
20
  csv2 = CSV.read(file2)
21
21
 
22
22
  unless csv1.length == csv2.length && csv1[0].length == csv2[0].length
23
- return puts "\nError: ".bold.red + "The files have different formats and cannot be compared"
23
+ return puts("\nError: ".bold.red + "The files have different formats and cannot be compared")
24
24
  end
25
25
 
26
- @table.add_row csv2[0].map(&:bold)
26
+ @table.add_row(csv2[0].map(&:bold))
27
27
  @table.add_separator
28
28
 
29
29
  generate_diff(csv1, csv2).drop(1).each do |row|
30
- @table.add_row [row[0].bold] + row[1..]
30
+ @table.add_row([row[0].bold] + row[1..])
31
31
  end
32
32
 
33
- puts @table
33
+ puts(@table)
34
34
  end
35
35
 
36
36
  def header?(row_num)
@@ -40,7 +40,8 @@ module HledgerForecast
40
40
  def generate_diff(csv1, csv2)
41
41
  csv1.each_with_index.map do |row, i|
42
42
  row.each_with_index.map do |cell, j|
43
- if header?(i) || j == 0 # Checking for the first column here
43
+ # Checking for the first column here
44
+ if header?(i) || j == 0
44
45
  csv2[i][j]
45
46
  else
46
47
  difference = parse_money(csv2[i][j]) - parse_money(cell)
@@ -66,17 +67,17 @@ module HledgerForecast
66
67
  end
67
68
 
68
69
  def parse_money(value)
69
- return 0.0 if value.strip == '0'
70
+ return 0.0 if value.strip == "0"
70
71
 
71
- value.gsub(/[^0-9.-]/, '').to_f
72
+ value.gsub(/[^0-9.-]/, "").to_f
72
73
  end
73
74
 
74
75
  def format_difference(amount, currency)
75
76
  formatted_amount = if currency.nil?
76
- format("%.2f", amount)
77
- else
78
- Formatter.format_money(amount, { currency: currency })
79
- end
77
+ format("%.2f", amount)
78
+ else
79
+ Formatter.format_money(amount, Settings.parse([], {currency: currency}))
80
+ end
80
81
 
81
82
  return formatted_amount if amount == 0
82
83
 
@@ -0,0 +1,29 @@
1
+ module HledgerForecast
2
+ class Forecast
3
+ attr_reader :transactions, :settings
4
+
5
+ def self.parse(csv_string, cli_options = nil)
6
+ rows = CSV.parse(
7
+ csv_string,
8
+ headers: true,
9
+ header_converters: -> (h) { h.to_s.tr("-", "_").to_sym },
10
+ converters: :numeric
11
+ )
12
+
13
+ settings = Settings.parse(rows.select { |r| r[:type] == "settings" }, cli_options)
14
+ transactions = rows.reject { |r| r[:type] == "settings" }.map { |r| Transaction.from_row(r) }
15
+
16
+ new(transactions, settings, rows.headers.include?(:tag))
17
+ end
18
+
19
+ def has_tags_column? = @has_tags_column
20
+
21
+ private
22
+
23
+ def initialize(transactions, settings, has_tags_column)
24
+ @transactions = transactions
25
+ @settings = settings
26
+ @has_tags_column = has_tags_column
27
+ end
28
+ end
29
+ end
@@ -1,24 +1,22 @@
1
1
  module HledgerForecast
2
- # Formats various items used throughout the application
3
2
  class Formatter
4
3
  def self.format_money(amount, settings)
5
- Money.from_cents(amount.to_f * 100, (settings[:currency]) || 'USD').format(
6
- symbol: settings[:show_symbol] || true,
7
- sign_before_symbol: settings[:sign_before_symbol] || false,
8
- thousands_separator: settings[:thousands_separator] ? ',' : nil
4
+ Money.from_cents(amount.to_f * 100, settings.currency).format(
5
+ symbol: settings.show_symbol,
6
+ sign_before_symbol: settings.sign_before_symbol,
7
+ thousands_separator: resolve_separator(settings.thousands_separator)
9
8
  )
10
9
  end
11
10
 
12
- def self.output_to_ledger(*compiled_data)
13
- output = compiled_data.compact.map do |data|
14
- data.map do |item|
15
- next unless item[:transactions].any?
16
-
17
- item[:header] + item[:transactions].join + item[:footer]
18
- end.join
19
- end.join("\n")
20
-
21
- output.gsub(/\n{2,}/, "\n\n")
11
+ private_class_method def self.resolve_separator(value)
12
+ case value
13
+ when "false", false, nil
14
+ nil
15
+ when "true", true
16
+ ","
17
+ else
18
+ value
19
+ end
22
20
  end
23
21
  end
24
22
  end
@@ -1,83 +1,43 @@
1
1
  module HledgerForecast
2
- # Generate forecasts for hledger from a yaml config file
3
2
  class Generator
4
- def self.generate(config, cli_options = nil)
5
- new.generate(config, cli_options)
6
- end
7
-
8
- def generate(config, cli_options = nil)
9
- forecast = CSV.parse(config, headers: true)
10
- @settings = Settings.config(forecast, cli_options)
11
-
12
- processed = []
13
- forecast.each do |row|
14
- next if row['type'] == "settings"
3
+ def self.generate(csv_string, cli_options = nil)
4
+ forecast = Forecast.parse(csv_string, cli_options)
5
+ transactions = forecast.transactions
15
6
 
16
- processed.push(process_forecast(row))
7
+ if cli_options&.dig(:tags)
8
+ raise "The --tags option requires a 'tag' column in the forecast CSV" unless forecast.has_tags_column?
9
+ transactions = transactions.select { |t| t.matches_tags?(cli_options[:tags]) }
17
10
  end
18
11
 
19
- unless @settings[:verbose]
20
- processed = processed.group_by do |row|
21
- [row[:type], row[:frequency], row[:from], row[:to], row[:account], row[:track]]
22
- end
23
-
24
- processed = processed.map do |(type, frequency, from, to, account, track), transactions|
25
- {
26
- type: type,
27
- frequency: frequency,
28
- from: from,
29
- to: to,
30
- account: account,
31
- track: track || false,
32
- transactions: transactions
33
- }
34
- end
35
- end
36
-
37
- Formatter.output_to_ledger(
38
- Transactions::Default.generate(processed, @settings),
39
- Transactions::Trackers.generate(processed, @settings)
40
- )
41
- end
42
-
43
- private
44
-
45
- def process_forecast(row)
46
- row['amount'] = Utilities.convert_amount(row['amount'])
47
-
48
- {
49
- type: row['type'],
50
- frequency: row['frequency'] || nil,
51
- account: row['account'],
52
- from: Date.parse(row['from']),
53
- to: row['to'] ? Calculator.new.evaluate_date(Date.parse(row['from']), row['to']) : nil,
54
- description: row['description'],
55
- category: row['category'],
56
- amount: Formatter.format_money(Calculator.new.evaluate(row['amount']), @settings),
57
- track: Transactions::Trackers.track?(row, @settings) ? true : false
58
- }
12
+ Transactions::Default.render(build_groups(transactions, forecast.settings), forecast.settings)
59
13
  end
60
14
 
61
- def transform_data(data)
62
- transformed_data = []
63
-
64
- data.each do |group_key, transactions|
65
- next if group_key == "settings"
66
-
67
- split_keys = group_key.split("@@")
68
-
69
- group_info = {
70
- type: split_keys[0],
71
- from: split_keys[1],
72
- to: split_keys[2],
73
- account: split_keys[3],
74
- transactions: transactions
75
- }
76
-
77
- transformed_data << group_info
15
+ private_class_method def self.build_groups(transactions, settings)
16
+ if settings.verbose?
17
+ transactions.map do |t|
18
+ TransactionGroup.new(
19
+ type: t.type,
20
+ frequency: t.frequency,
21
+ account: t.account,
22
+ from: t.from,
23
+ to: t.to,
24
+ transactions: [t]
25
+ )
26
+ end
27
+ else
28
+ transactions
29
+ .group_by { |t| [t.type, t.frequency, t.from, t.to, t.account] }
30
+ .map do |(type, frequency, from, to, account), txns|
31
+ TransactionGroup.new(
32
+ type: type,
33
+ frequency: frequency,
34
+ account: account,
35
+ from: from,
36
+ to: to,
37
+ transactions: txns
38
+ )
39
+ end
78
40
  end
79
-
80
- transformed_data
81
41
  end
82
42
  end
83
43
  end