ynab_convert 1.0.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 +76 -5
  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 +4 -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.6'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/ynab_convert.rb CHANGED
@@ -3,7 +3,8 @@
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']
7
8
 
8
9
  # The application
9
10
  module YnabConvert
@@ -32,7 +33,7 @@ module YnabConvert
32
33
 
33
34
  begin
34
35
  @processor = opts[:processor].new(
35
- file: @file
36
+ filepath: @file
36
37
  )
37
38
  rescue Errno::ENOENT
38
39
  handle_file_not_found
@@ -98,7 +99,7 @@ module YnabConvert
98
99
  end
99
100
 
100
101
  def processor_class_name
101
- "Processor::#{@options[:institution].camel_case}"
102
+ "Processors::#{@options[:institution].camel_case}"
102
103
  end
103
104
 
104
105
  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.6
4
+ version: 2.0.0
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