ynab_convert 1.0.7 → 2.0.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +10 -2
  4. data/Gemfile.lock +37 -12
  5. data/Guardfile +1 -29
  6. data/README.md +82 -7
  7. data/lib/ynab_convert/api_clients/api_client.rb +24 -0
  8. data/lib/ynab_convert/api_clients/currency_api.rb +66 -0
  9. data/lib/ynab_convert/documents/statements/example_statement.rb +16 -0
  10. data/lib/ynab_convert/documents/statements/n26_statement.rb +24 -0
  11. data/lib/ynab_convert/documents/statements/statement.rb +39 -0
  12. data/lib/ynab_convert/documents/statements/ubs_chequing_statement.rb +20 -0
  13. data/lib/ynab_convert/documents/statements/ubs_credit_statement.rb +19 -0
  14. data/lib/ynab_convert/documents/statements/wise_statement.rb +17 -0
  15. data/lib/ynab_convert/documents/ynab4_files/ynab4_file.rb +58 -0
  16. data/lib/ynab_convert/documents.rb +17 -0
  17. data/lib/ynab_convert/logger.rb +1 -1
  18. data/lib/ynab_convert/processors/example_processor.rb +24 -0
  19. data/lib/ynab_convert/processors/n26_processor.rb +26 -0
  20. data/lib/ynab_convert/processors/processor.rb +75 -0
  21. data/lib/ynab_convert/processors/ubs_chequing_processor.rb +21 -0
  22. data/lib/ynab_convert/processors/ubs_credit_processor.rb +17 -0
  23. data/lib/ynab_convert/processors/wise_processor.rb +19 -0
  24. data/lib/ynab_convert/processors.rb +2 -2
  25. data/lib/ynab_convert/transformers/cleaners/cleaner.rb +17 -0
  26. data/lib/ynab_convert/transformers/cleaners/n26_cleaner.rb +13 -0
  27. data/lib/ynab_convert/transformers/cleaners/ubs_chequing_cleaner.rb +98 -0
  28. data/lib/ynab_convert/transformers/cleaners/ubs_credit_cleaner.rb +45 -0
  29. data/lib/ynab_convert/transformers/cleaners/wise_cleaner.rb +39 -0
  30. data/lib/ynab_convert/transformers/enhancers/enhancer.rb +20 -0
  31. data/lib/ynab_convert/transformers/enhancers/n26_enhancer.rb +74 -0
  32. data/lib/ynab_convert/transformers/enhancers/wise_enhancer.rb +87 -0
  33. data/lib/ynab_convert/transformers/formatters/example_formatter.rb +12 -0
  34. data/lib/ynab_convert/transformers/formatters/formatter.rb +91 -0
  35. data/lib/ynab_convert/transformers/formatters/n26_formatter.rb +19 -0
  36. data/lib/ynab_convert/transformers/formatters/ubs_chequing_formatter.rb +12 -0
  37. data/lib/ynab_convert/transformers/formatters/ubs_credit_formatter.rb +12 -0
  38. data/lib/ynab_convert/transformers/formatters/wise_formatter.rb +35 -0
  39. data/lib/ynab_convert/transformers.rb +18 -0
  40. data/lib/ynab_convert/validators/ynab4_row_validator.rb +83 -0
  41. data/lib/ynab_convert/validators.rb +9 -0
  42. data/lib/ynab_convert/version.rb +1 -1
  43. data/lib/ynab_convert.rb +22 -3
  44. data/ynab_convert.gemspec +4 -0
  45. metadata +91 -8
  46. data/lib/ynab_convert/processor/base.rb +0 -226
  47. data/lib/ynab_convert/processor/example.rb +0 -124
  48. data/lib/ynab_convert/processor/n26.rb +0 -70
  49. data/lib/ynab_convert/processor/revolut.rb +0 -103
  50. data/lib/ynab_convert/processor/ubs_chequing.rb +0 -115
  51. data/lib/ynab_convert/processor/ubs_credit.rb +0 -83
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ynab_convert/transformers'
4
+
5
+ module Transformers
6
+ module Formatters
7
+ # Formats N26 statement values to YNAB4 value
8
+ class N26 < Formatter
9
+ def initialize
10
+ super({ date: [0], payee: [1], amount: [5] })
11
+ end
12
+
13
+ # All amounts are always in EUR
14
+ def memo(_row)
15
+ 'EUR'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Transformers
4
+ module Formatters
5
+ # UBS Switzerland Chequing accounts formatter
6
+ class UBSChequing < Formatter
7
+ def initialize
8
+ super({ date: [9], payee: [12], outflow: [18], inflow: [19] })
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Transformers
4
+ module Formatters
5
+ # UBS Switzerland Credit Card accounts formatter
6
+ class UBSCredit < Formatter
7
+ def initialize
8
+ super({ date: [3], payee: [4], outflow: [10], inflow: [11] })
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Transformers
4
+ module Formatters
5
+ # Wise card accounts formatter
6
+ class Wise < Formatter
7
+ def initialize
8
+ super({ date: [1], payee: [13], amount: [2] })
9
+ end
10
+
11
+ def payee(row)
12
+ merchant = row[13]
13
+ description = row[4]
14
+
15
+ return description if merchant.nil?
16
+
17
+ merchant
18
+ end
19
+
20
+ def memo(row)
21
+ # Description goes in Memo because we'll need to extract the original
22
+ # amount from it in the enhancer.
23
+ description = row[4]
24
+ amount_currency = row[3]
25
+ original_amount = description.scan(/\d+\.\d{2}\s\w{3}/).first
26
+
27
+ memo = amount_currency
28
+ # Topups don't have an original amount
29
+ memo = "#{memo},#{original_amount}" unless original_amount.nil?
30
+
31
+ memo
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Regroups all the classes involved in transforming a given Statement into a
4
+ # YNAB4File
5
+ module Transformers
6
+ transformers = %w[cleaner enhancer formatter]
7
+
8
+ # Load all known Transformers
9
+ transformers.each do |t|
10
+ # Require the base classes first so that its children can find the parent
11
+ # class since files are otherwise loaded in alphabetical order
12
+ require File.join(__dir__, 'transformers', "#{t}s", "#{t}.rb")
13
+
14
+ Dir[File.join(__dir__, 'transformers', "#{t}s", '*.rb')].sort.each do |file|
15
+ require file
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Validators
4
+ # Checks YNAB4 row for validity. A row is valid if it has a Date, Payee, and
5
+ # one of Amount, Outflow, Inflow.
6
+ module YNAB4Row
7
+ # Validates a row
8
+ # @param row [Array<String, Numeric, Date>] The row to validate
9
+ # @return [Boolean] Whether the row is valid
10
+ def self.valid?(row)
11
+ # we are dealing with a YNAB4 row:
12
+ # %w[Date Payee Memo Amount|Outflow Inflow]
13
+ amount_valid?(row) &&
14
+ transaction_date_valid?(row) &&
15
+ payee_valid?(row)
16
+ end
17
+
18
+ # Indicates which format the row is in (:flows or :amounts)
19
+ # @param row [CSV::Row] the row to check
20
+ # @return [:flows, :amounts] the row's format
21
+ def self.row_format(row)
22
+ format = :flows
23
+ # :flows has 5 columns: Date, Payee, Memo, Outflow, Inflow
24
+ # :amounts has 4 columns: Date, Payee, Memo, Amount
25
+ format = :amounts if row.length == 4
26
+
27
+ format
28
+ end
29
+
30
+ # Indicates whether the amount on the row is valid
31
+ # @param row [CSV::Row] the row to check
32
+ # @return [Boolean] whether the amount is invalid
33
+ def self.amount_valid?(row)
34
+ format = row_format(row)
35
+ indices = [3]
36
+ indices << 4 if format == :flows
37
+
38
+ if format == :amounts
39
+ return indices.reduce(true) do |valid, i|
40
+ valid && value_valid?(row[i])
41
+ end
42
+ end
43
+
44
+ indices.reduce(false) do |valid, i|
45
+ valid || value_valid?(row[i])
46
+ end
47
+ end
48
+
49
+ # Indicates whether a value is valid
50
+ # @note Prefer using the #valid? method
51
+ # @param value [#zero?, #nil?, #to_s] the value to check
52
+ # @return [Boolean] whether the value is valid
53
+ def self.value_valid?(value)
54
+ if value.respond_to? :zero?
55
+ !value.zero?
56
+ else
57
+ !value.nil? && !value.to_s.empty?
58
+ end
59
+ end
60
+
61
+ # Validates the Date value
62
+ # @note Prefer using the #valid? method
63
+ # @param row [Array<String, Numeric, Date] The row to validate
64
+ # @return [Boolean] Whether the row's Date is invalid
65
+ def self.transaction_date_valid?(row)
66
+ date_index = 0
67
+ date = row[date_index]
68
+
69
+ value_valid?(date)
70
+ end
71
+
72
+ # Validates the Payee value
73
+ # @note Prefer using the #valid? method
74
+ # @param row [Array<String, Numeric, Date] The row to validate
75
+ # @return [Boolean] Whether the row's Payee is valid
76
+ def self.payee_valid?(row)
77
+ payee_index = 1
78
+ payee = row[payee_index]
79
+
80
+ value_valid?(payee)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Regroups all the row validators
4
+ module Validators
5
+ # Load all known Validators
6
+ Dir[File.join(__dir__, 'validators', '*.rb')].sort.each do |file|
7
+ require file
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module YnabConvert
4
- VERSION = '1.0.7'
4
+ VERSION = '2.0.1'
5
5
  end
data/lib/ynab_convert.rb CHANGED
@@ -3,7 +3,11 @@
3
3
  require 'ynab_convert/version'
4
4
  require 'slop'
5
5
  require 'ynab_convert/logger'
6
- require 'core_extensions/string.rb'
6
+ require 'core_extensions/string'
7
+ require 'byebug' if ENV['YNAB_CONVERT_DEBUG']
8
+
9
+ # FIXME: The architecture in here is not the greatest... It should be
10
+ # redesigned entirely.
7
11
 
8
12
  # The application
9
13
  module YnabConvert
@@ -32,7 +36,7 @@ module YnabConvert
32
36
 
33
37
  begin
34
38
  @processor = opts[:processor].new(
35
- file: @file
39
+ filepath: @file
36
40
  )
37
41
  rescue Errno::ENOENT
38
42
  handle_file_not_found
@@ -98,7 +102,22 @@ module YnabConvert
98
102
  end
99
103
 
100
104
  def processor_class_name
101
- "Processor::#{@options[:institution].camel_case}"
105
+ # Processor class names don't always match camelcasing the `-i` argument
106
+ # from the command line. For those classes that don't, a lookup is
107
+ # performed to find the proper class name.
108
+ institution = @options[:institution].to_sym
109
+ institution_to_classname = {
110
+ ubs_chequing: 'UBSChequing',
111
+ ubs_credit: 'UBSCredit'
112
+ }
113
+
114
+ classname = institution_to_classname.fetch(institution) do |el|
115
+ # If the class name is "regular", it will be found by camelcasing the
116
+ # name passed as the `-i` argument from the command line.
117
+ el.to_s.camel_case
118
+ end
119
+
120
+ "Processors::#{classname}"
102
121
  end
103
122
 
104
123
  def processor
data/ynab_convert.gemspec CHANGED
@@ -59,9 +59,13 @@ Gem::Specification.new do |spec|
59
59
  spec.add_development_dependency 'rspec-core'
60
60
  spec.add_development_dependency 'rubocop'
61
61
  spec.add_development_dependency 'rubocop-rake'
62
+ spec.add_development_dependency 'rubocop-rspec'
62
63
  spec.add_development_dependency 'simplecov'
63
64
  spec.add_development_dependency 'solargraph'
65
+ spec.add_development_dependency 'vcr'
66
+ spec.add_development_dependency 'webmock'
64
67
 
65
68
  spec.add_dependency 'i18n'
66
69
  spec.add_dependency 'slop'
70
+ spec.add_dependency 'timecop'
67
71
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ynab_convert
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.7
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - coaxial
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-06 00:00:00.000000000 Z
11
+ date: 2022-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop-rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: simplecov
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +178,34 @@ dependencies:
164
178
  - - ">="
165
179
  - !ruby/object:Gem::Version
166
180
  version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: vcr
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: webmock
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
167
209
  - !ruby/object:Gem::Dependency
168
210
  name: i18n
169
211
  requirement: !ruby/object:Gem::Requirement
@@ -192,6 +234,20 @@ dependencies:
192
234
  - - ">="
193
235
  - !ruby/object:Gem::Version
194
236
  version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: timecop
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ type: :runtime
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
195
251
  description: |
196
252
  Utility to convert CSV statements into the YNAB4 format for easier
197
253
  transation import. Supports several banks and can easily be extended to
@@ -224,15 +280,42 @@ files:
224
280
  - lib/core_extensions/string.rb
225
281
  - lib/slop/symbol.rb
226
282
  - lib/ynab_convert.rb
283
+ - lib/ynab_convert/api_clients/api_client.rb
284
+ - lib/ynab_convert/api_clients/currency_api.rb
285
+ - lib/ynab_convert/documents.rb
286
+ - lib/ynab_convert/documents/statements/example_statement.rb
287
+ - lib/ynab_convert/documents/statements/n26_statement.rb
288
+ - lib/ynab_convert/documents/statements/statement.rb
289
+ - lib/ynab_convert/documents/statements/ubs_chequing_statement.rb
290
+ - lib/ynab_convert/documents/statements/ubs_credit_statement.rb
291
+ - lib/ynab_convert/documents/statements/wise_statement.rb
292
+ - lib/ynab_convert/documents/ynab4_files/ynab4_file.rb
227
293
  - lib/ynab_convert/error.rb
228
294
  - lib/ynab_convert/logger.rb
229
- - lib/ynab_convert/processor/base.rb
230
- - lib/ynab_convert/processor/example.rb
231
- - lib/ynab_convert/processor/n26.rb
232
- - lib/ynab_convert/processor/revolut.rb
233
- - lib/ynab_convert/processor/ubs_chequing.rb
234
- - lib/ynab_convert/processor/ubs_credit.rb
235
295
  - lib/ynab_convert/processors.rb
296
+ - lib/ynab_convert/processors/example_processor.rb
297
+ - lib/ynab_convert/processors/n26_processor.rb
298
+ - lib/ynab_convert/processors/processor.rb
299
+ - lib/ynab_convert/processors/ubs_chequing_processor.rb
300
+ - lib/ynab_convert/processors/ubs_credit_processor.rb
301
+ - lib/ynab_convert/processors/wise_processor.rb
302
+ - lib/ynab_convert/transformers.rb
303
+ - lib/ynab_convert/transformers/cleaners/cleaner.rb
304
+ - lib/ynab_convert/transformers/cleaners/n26_cleaner.rb
305
+ - lib/ynab_convert/transformers/cleaners/ubs_chequing_cleaner.rb
306
+ - lib/ynab_convert/transformers/cleaners/ubs_credit_cleaner.rb
307
+ - lib/ynab_convert/transformers/cleaners/wise_cleaner.rb
308
+ - lib/ynab_convert/transformers/enhancers/enhancer.rb
309
+ - lib/ynab_convert/transformers/enhancers/n26_enhancer.rb
310
+ - lib/ynab_convert/transformers/enhancers/wise_enhancer.rb
311
+ - lib/ynab_convert/transformers/formatters/example_formatter.rb
312
+ - lib/ynab_convert/transformers/formatters/formatter.rb
313
+ - lib/ynab_convert/transformers/formatters/n26_formatter.rb
314
+ - lib/ynab_convert/transformers/formatters/ubs_chequing_formatter.rb
315
+ - lib/ynab_convert/transformers/formatters/ubs_credit_formatter.rb
316
+ - lib/ynab_convert/transformers/formatters/wise_formatter.rb
317
+ - lib/ynab_convert/validators.rb
318
+ - lib/ynab_convert/validators/ynab4_row_validator.rb
236
319
  - lib/ynab_convert/version.rb
237
320
  - ynab_convert.gemspec
238
321
  homepage: https://github.com/coaxial/ynab_convert
@@ -1,226 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'core_extensions/string'
4
- require 'csv'
5
- require 'ynab_convert/logger'
6
-
7
- module Processor
8
- # Base class for a Processor, all processors must inherit from it.
9
-
10
- # rubocop:disable Metrics/ClassLength
11
- class Base
12
- include YnabLogger
13
- include CoreExtensions::String::Inflections
14
-
15
- attr_reader :loader_options
16
-
17
- # @option options [String] :file Path to the CSV file to process
18
- # @option options [Symbol] :format YNAB4 format to use, one of :flows or
19
- # :amounts. :flows is useful for CSVs with separate debit and credit
20
- # columns, :amounts is for CSVs with only one amount columns and +/-
21
- # numbers. See
22
- # https://docs.youneedabudget.com/article/921-formatting-csv-file
23
- def initialize(options)
24
- default_options = { file: '', format: :flows }
25
- opts = default_options.merge(options)
26
-
27
- logger.debug "Initializing processor with options: `#{opts.to_h}'"
28
- raise ::Errno::ENOENT unless File.exist? opts[:file]
29
-
30
- @file = opts[:file]
31
- @headers = { transaction_date: nil, payee: nil }
32
- @format = opts[:format]
33
-
34
- if @format == :amounts
35
- amounts_columns = { amount: nil }
36
- @headers.merge!(amounts_columns)
37
- else
38
- flows_columns = { inflow: nil, outflow: nil }
39
- @headers.merge!(flows_columns)
40
- end
41
- end
42
-
43
- def to_ynab!
44
- begin
45
- convert!
46
- rename_file
47
- rescue YnabConvert::Error
48
- invalid_csv_file
49
- end
50
- ensure
51
- logger.debug "Deleting temp file `#{temp_filename}'"
52
- delete_temp_csv
53
- end
54
-
55
- protected
56
-
57
- attr_accessor :statement_from, :statement_to, :headers
58
-
59
- def amount_invalid?(row)
60
- amount_index = 3
61
-
62
- # If there is no amount,
63
- # then the row is invalid.
64
- row[amount_index].nil? || row[amount_index].empty?
65
- end
66
-
67
- def inflow_outflow_invalid?(row)
68
- inflow_index = 3
69
- outflow_index = 4
70
-
71
- # If there is neither inflow and outflow values,
72
- # or both the inflow and outflow amounts are 0,
73
- # then the row is invalid.
74
- (
75
- row[inflow_index].nil? ||
76
- row[inflow_index].empty? ||
77
- row[inflow_index] == '0.00'
78
- ) && (
79
- row[outflow_index].nil? ||
80
- row[outflow_index].empty? ||
81
- row[outflow_index] == '0.00'
82
- )
83
- end
84
-
85
- def amounts_missing?(row)
86
- logger.debug "Checking for missing amount in `#{row}`"
87
- if @format == :amounts
88
- logger.debug 'Using `:amounts`'
89
- amount_invalid?(row)
90
- else
91
- logger.debug 'Using `:flows`'
92
- inflow_outflow_invalid?(row)
93
- end
94
- end
95
-
96
- def skip_row(row)
97
- logger.debug "Found empty row, skipping it: #{row.to_h}"
98
- throw :skip_row
99
- end
100
-
101
- def delete_temp_csv
102
- FileUtils.remove_file temp_filename, force: true
103
- end
104
-
105
- def transaction_date_missing?(ynab_row)
106
- ynab_row[0].nil? || [0].empty?
107
- end
108
-
109
- def extract_transaction_date(ynab_row)
110
- transaction_date_index = 0
111
- ynab_row[transaction_date_index]
112
- end
113
-
114
- def record_statement_interval_dates(ynab_row)
115
- transaction_date_index = 0
116
- date = Date.parse(ynab_row[transaction_date_index])
117
-
118
- if date_is_further_away?(date)
119
- logger.debug "Replacing statement_from `#{statement_from.inspect}' "\
120
- "with `#{date}'"
121
- self.statement_from = date
122
- end
123
- # rubocop:disable Style/GuardClause
124
- if date_is_more_recent?(date)
125
- logger.debug "Replacing statement_to `#{statement_to.inspect}' with "\
126
- "`#{date}'"
127
- self.statement_to = date
128
- end
129
- # rubocop:enable Style/GuardClause
130
- end
131
-
132
- def date_is_more_recent?(date)
133
- statement_to.nil? || statement_to < date
134
- end
135
-
136
- def date_is_further_away?(date)
137
- statement_from.nil? || statement_from > date
138
- end
139
-
140
- def convert!
141
- logger.debug "Will write to `#{temp_filename}'"
142
-
143
- logger.debug(loader_options)
144
- CSV.open(temp_filename, 'wb', **output_options) do |converted|
145
- CSV.foreach(@file, 'rb', **loader_options) do |row|
146
- logger.debug "Parsing row: `#{row.to_h}'"
147
- # Some rows don't contain valid or useful data
148
- catch :skip_row do
149
- extract_header_names(row)
150
- ynab_row = transformers(row)
151
- if amounts_missing?(ynab_row) ||
152
- transaction_date_missing?(ynab_row)
153
- logger.debug 'Empty row, skipping it'
154
- skip_row(row)
155
- end
156
- converted << ynab_row
157
- record_statement_interval_dates(ynab_row)
158
- end
159
-
160
- logger.debug 'Done converting'
161
- end
162
- end
163
- end
164
-
165
- def rename_file
166
- File.rename(temp_filename, output_filename)
167
- logger.debug "Renamed temp file `#{temp_filename}' to "\
168
- "`#{output_filename}'"
169
- end
170
-
171
- def invalid_csv_file
172
- raise YnabConvert::Error, "Unable to parse file `#{@file}'. Is it a "\
173
- "valid CSV file from #{@institution_name}?"
174
- end
175
-
176
- def file_uid
177
- @file_uid ||= rand(36**8).to_s(36)
178
- end
179
-
180
- def temp_filename
181
- "#{File.basename(@file, '.csv')}_#{@institution_name.snake_case}_"\
182
- "#{file_uid}_ynab4.csv"
183
- end
184
-
185
- def output_filename
186
- # If the file contained no parsable CSV data, from and to dates will be
187
- # nil.
188
- # This is to avoid a NoMethodError on NilClass.
189
- raise YnabConvert::Error if statement_from.nil? || statement_to.nil?
190
-
191
- from = statement_from.strftime('%Y%m%d')
192
- to = statement_to.strftime('%Y%m%d')
193
-
194
- "#{File.basename(@file, '.csv')}_#{@institution_name.snake_case}_"\
195
- "#{from}-#{to}_ynab4.csv"
196
- end
197
-
198
- def ynab_headers
199
- common_headers = %w[Date Payee Memo]
200
-
201
- if @format == :amounts
202
- amounts_headers = %w[Amount]
203
- common_headers.concat(amounts_headers)
204
- else
205
- flows_headers = %w[Outflow Inflow]
206
- common_headers.concat(flows_headers)
207
- end
208
-
209
- common_headers
210
- end
211
-
212
- def output_options
213
- {
214
- converters: %i[numeric date],
215
- force_quotes: true,
216
- write_headers: true,
217
- headers: ynab_headers
218
- }
219
- end
220
-
221
- def transformers
222
- raise NotImplementedError, :transformers
223
- end
224
- end
225
- # rubocop:enable Metrics/ClassLength
226
- end