ynab_convert 1.0.6 → 2.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.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.rubocop.yml +10 -2
- data/Gemfile.lock +37 -12
- data/Guardfile +1 -29
- data/README.md +76 -5
- data/lib/ynab_convert/api_clients/api_client.rb +24 -0
- data/lib/ynab_convert/api_clients/currency_api.rb +66 -0
- data/lib/ynab_convert/documents/statements/example_statement.rb +16 -0
- data/lib/ynab_convert/documents/statements/n26_statement.rb +24 -0
- data/lib/ynab_convert/documents/statements/statement.rb +39 -0
- data/lib/ynab_convert/documents/statements/ubs_chequing_statement.rb +20 -0
- data/lib/ynab_convert/documents/statements/ubs_credit_statement.rb +19 -0
- data/lib/ynab_convert/documents/statements/wise_statement.rb +17 -0
- data/lib/ynab_convert/documents/ynab4_files/ynab4_file.rb +58 -0
- data/lib/ynab_convert/documents.rb +17 -0
- data/lib/ynab_convert/logger.rb +1 -1
- data/lib/ynab_convert/processors/example_processor.rb +24 -0
- data/lib/ynab_convert/processors/n26_processor.rb +26 -0
- data/lib/ynab_convert/processors/processor.rb +75 -0
- data/lib/ynab_convert/processors/ubs_chequing_processor.rb +21 -0
- data/lib/ynab_convert/processors/ubs_credit_processor.rb +17 -0
- data/lib/ynab_convert/processors/wise_processor.rb +19 -0
- data/lib/ynab_convert/processors.rb +2 -2
- data/lib/ynab_convert/transformers/cleaners/cleaner.rb +17 -0
- data/lib/ynab_convert/transformers/cleaners/n26_cleaner.rb +13 -0
- data/lib/ynab_convert/transformers/cleaners/ubs_chequing_cleaner.rb +98 -0
- data/lib/ynab_convert/transformers/cleaners/ubs_credit_cleaner.rb +45 -0
- data/lib/ynab_convert/transformers/cleaners/wise_cleaner.rb +39 -0
- data/lib/ynab_convert/transformers/enhancers/enhancer.rb +20 -0
- data/lib/ynab_convert/transformers/enhancers/n26_enhancer.rb +74 -0
- data/lib/ynab_convert/transformers/enhancers/wise_enhancer.rb +87 -0
- data/lib/ynab_convert/transformers/formatters/example_formatter.rb +12 -0
- data/lib/ynab_convert/transformers/formatters/formatter.rb +91 -0
- data/lib/ynab_convert/transformers/formatters/n26_formatter.rb +19 -0
- data/lib/ynab_convert/transformers/formatters/ubs_chequing_formatter.rb +12 -0
- data/lib/ynab_convert/transformers/formatters/ubs_credit_formatter.rb +12 -0
- data/lib/ynab_convert/transformers/formatters/wise_formatter.rb +35 -0
- data/lib/ynab_convert/transformers.rb +18 -0
- data/lib/ynab_convert/validators/ynab4_row_validator.rb +83 -0
- data/lib/ynab_convert/validators.rb +9 -0
- data/lib/ynab_convert/version.rb +1 -1
- data/lib/ynab_convert.rb +4 -3
- data/ynab_convert.gemspec +4 -0
- metadata +91 -8
- data/lib/ynab_convert/processor/base.rb +0 -226
- data/lib/ynab_convert/processor/example.rb +0 -124
- data/lib/ynab_convert/processor/n26.rb +0 -70
- data/lib/ynab_convert/processor/revolut.rb +0 -103
- data/lib/ynab_convert/processor/ubs_chequing.rb +0 -115
- data/lib/ynab_convert/processor/ubs_credit.rb +0 -83
@@ -1,124 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Processor
|
4
|
-
# An example of how to implement a custom processor
|
5
|
-
# Processes CSV files with this format:
|
6
|
-
# <<~ROWS
|
7
|
-
# "Date","Payee","Memo","Outflow","Inflow"
|
8
|
-
# "23/12/2019","coaxial","","1000000.00",""
|
9
|
-
# "30/12/2019","Santa","","50000.00",""
|
10
|
-
# "02/02/2020","Someone Else","","45.00",""
|
11
|
-
# ROWS
|
12
|
-
# The file name for the processor should be the institution name in
|
13
|
-
# camel case. It's ok to skip "Bank" or "Credit Union" when naming the file
|
14
|
-
# if it's redundant. For instance, this parser is for "Example Bank" but it's
|
15
|
-
# named "example.rb", its corresponding spec is
|
16
|
-
# "spec/example_processor_spec.rb" and its fixture would be
|
17
|
-
# "spec/fixtures/example/statement.csv"
|
18
|
-
class Example < Processor::Base
|
19
|
-
# @option options [String] :file Path to the CSV file to process
|
20
|
-
def initialize(options)
|
21
|
-
# Custom converters can be added so that the CSV data is parsed when
|
22
|
-
# loading the original file
|
23
|
-
register_custom_converters
|
24
|
-
|
25
|
-
# These are the options for the CSV module (see
|
26
|
-
# https://ruby-doc.org/stdlib-2.6/libdoc/csv/rdoc/CSV.html#method-c-new)
|
27
|
-
# They should match the format for the CSV file that the financial
|
28
|
-
# institution generates.
|
29
|
-
@loader_options = {
|
30
|
-
col_sep: ';',
|
31
|
-
# Use your converters, if any
|
32
|
-
converters: %i[transaction_date my_converter],
|
33
|
-
headers: true
|
34
|
-
}
|
35
|
-
|
36
|
-
# This is the financial institution's full name as it calls itself. This
|
37
|
-
# usually matches the institution's letterhead and/or commercial name.
|
38
|
-
# It can happen that the same institution needs different parsers because
|
39
|
-
# its credit card CSV files are in one format, and its chequing accounts
|
40
|
-
# in another. In that case, more details can be added in parens.
|
41
|
-
# For instance:
|
42
|
-
# 'Example Bank (credit cards)' and 'Example Bank (chequing)'
|
43
|
-
@institution_name = 'Example Bank'
|
44
|
-
|
45
|
-
# This is mandatory.
|
46
|
-
super(options)
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def register_custom_converters
|
52
|
-
CSV::Converters[:transaction_date] = lambda { |s|
|
53
|
-
# Only match strings that have two digits, a dot, two digits, a dot,
|
54
|
-
# two digits, i.e. the dates in this institution's CSV files.
|
55
|
-
date_regex = /^\d{2}\.\d{2}\.\d{2}$/
|
56
|
-
|
57
|
-
if !s.nil? && s.match(date_regex)
|
58
|
-
parsed_date = Date.strptime(s, '%d.%m.%y')
|
59
|
-
logger.debug "Converted `#{s.inspect}' into date "\
|
60
|
-
"`#{parsed_date}'"
|
61
|
-
return parsed_date
|
62
|
-
end
|
63
|
-
|
64
|
-
s
|
65
|
-
}
|
66
|
-
|
67
|
-
CSV::Converters[:my_converter] = lambda { |s|
|
68
|
-
# A contrived example, just to illustrate multiple converters
|
69
|
-
if s.respond_to?(:downcase)
|
70
|
-
converted_s = s.downcase
|
71
|
-
logger.debug "Converted `#{s.inspect}' into downcased string "\
|
72
|
-
"`#{converted_s}'"
|
73
|
-
return converted_s
|
74
|
-
end
|
75
|
-
|
76
|
-
s
|
77
|
-
}
|
78
|
-
end
|
79
|
-
|
80
|
-
protected
|
81
|
-
|
82
|
-
# Converts the institution's CSV rows into YNAB4 rows.
|
83
|
-
# The YNAB4 columns are:
|
84
|
-
# "Date', "Payee", "Memo", "Outflow", "Inflow"
|
85
|
-
# which match Example Bank's "transaction_date" (after parsing),
|
86
|
-
# "beneficiary", nothing, "debit", and "credit" respectively.
|
87
|
-
# Note that Example Bank doesn't include any relevant column for YNAB4's
|
88
|
-
# "Memo" column so it's skipped and gets '' as its value.
|
89
|
-
def transformers(row)
|
90
|
-
# Convert the original transaction_date to DD/MM/YYYY as YNAB4 expects
|
91
|
-
# it.
|
92
|
-
unless row[headers[:transaction_date]].nil?
|
93
|
-
transaction_date = row[headers[:transaction_date]].strftime('%d/%m/%Y')
|
94
|
-
end
|
95
|
-
payee = row[headers[:payee]]
|
96
|
-
debit = row[headers[:debit]]
|
97
|
-
credit = row[headers[:credit]]
|
98
|
-
|
99
|
-
# CSV files can have funny data in them, including invalid or empty rows.
|
100
|
-
# These rows can be skipped from the converted YNAB4 file by calling
|
101
|
-
# skip_row when detected. In this particular case, if there is no
|
102
|
-
# transaction date, it means the row is empty or invalid and we discard
|
103
|
-
# it.
|
104
|
-
skip_row(row) if transaction_date.nil?
|
105
|
-
converted_row = [transaction_date, payee, nil, debit, credit]
|
106
|
-
logger.debug "Converted row: #{converted_row}"
|
107
|
-
converted_row
|
108
|
-
end
|
109
|
-
|
110
|
-
private
|
111
|
-
|
112
|
-
# Institutions love translating the column names, apparently. Rather than
|
113
|
-
# hardcoding the column name as a string, use the headers array at the
|
114
|
-
# right index.
|
115
|
-
# These lookups aren't particularly expensive but they're done on each row
|
116
|
-
# so why not memoize them with ||=
|
117
|
-
def extract_header_names(row)
|
118
|
-
headers[:transaction_date] ||= row.headers[0]
|
119
|
-
headers[:payee] ||= row.headers[2]
|
120
|
-
headers[:debit] ||= row.headers[3]
|
121
|
-
headers[:credit] ||= row.headers[4]
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Processor
|
4
|
-
# Processes CSV files from N26
|
5
|
-
class N26 < Processor::Base
|
6
|
-
# @option options [String] :file Path to the CSV file to process
|
7
|
-
def initialize(options)
|
8
|
-
# Custom converters can be added so that the CSV data is parsed when
|
9
|
-
# loading the original file
|
10
|
-
register_custom_converters
|
11
|
-
|
12
|
-
# These are the options for the CSV module (see
|
13
|
-
# https://ruby-doc.org/stdlib-2.6/libdoc/csv/rdoc/CSV.html#method-c-new)
|
14
|
-
# They should match the format for the CSV file that the financial
|
15
|
-
# institution generates.
|
16
|
-
@loader_options = {
|
17
|
-
col_sep: ',',
|
18
|
-
quote_char: '"',
|
19
|
-
# Use your converters, if any
|
20
|
-
# converters: %i[],
|
21
|
-
headers: true,
|
22
|
-
encoding: 'bom|utf-8'
|
23
|
-
}
|
24
|
-
|
25
|
-
# This is the financial institution's full name as it calls itself. This
|
26
|
-
# usually matches the institution's letterhead and/or commercial name.
|
27
|
-
# It can happen that the same institution needs different parsers because
|
28
|
-
# its credit card CSV files are in one format, and its chequing accounts
|
29
|
-
# in another. In that case, more details can be added in parens.
|
30
|
-
# For instance:
|
31
|
-
# 'Example Bank (credit cards)' and 'Example Bank (chequing)'
|
32
|
-
@institution_name = 'N26 Bank'
|
33
|
-
# N26's CSV only has one columns for all transactions instead of separate
|
34
|
-
# debit and credit columns
|
35
|
-
additional_processor_options = { format: :amounts }
|
36
|
-
|
37
|
-
# This is mandatory.
|
38
|
-
super(options.merge(additional_processor_options))
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def register_custom_converters; end
|
44
|
-
|
45
|
-
protected
|
46
|
-
|
47
|
-
def transformers(row)
|
48
|
-
transaction_date = row[headers[:transaction_date]]
|
49
|
-
payee = row[headers[:payee]]
|
50
|
-
amount = row[headers[:amount]]
|
51
|
-
|
52
|
-
converted_row = [transaction_date, payee, nil, amount]
|
53
|
-
logger.debug "Converted row: #{converted_row}"
|
54
|
-
converted_row
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
# Institutions love translating the column names, apparently. Rather than
|
60
|
-
# hardcoding the column name as a string, use the headers array at the
|
61
|
-
# right index.
|
62
|
-
# These lookups aren't particularly expensive but they're done on each row
|
63
|
-
# so why not memoize them with ||=
|
64
|
-
def extract_header_names(row)
|
65
|
-
headers[:transaction_date] ||= row.headers[0]
|
66
|
-
headers[:payee] ||= row.headers[1]
|
67
|
-
headers[:amount] ||= row.headers[5]
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,103 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'i18n'
|
4
|
-
|
5
|
-
module Processor
|
6
|
-
# Processes CSV files from Revolut
|
7
|
-
class Revolut < Processor::Base
|
8
|
-
# @option options [String] :file Path to the CSV file to process
|
9
|
-
def initialize(options)
|
10
|
-
register_custom_converters
|
11
|
-
@loader_options = {
|
12
|
-
col_sep: ';',
|
13
|
-
converters: %i[amounts transaction_dates],
|
14
|
-
quote_char: nil,
|
15
|
-
encoding: Encoding::UTF_8,
|
16
|
-
headers: true
|
17
|
-
}
|
18
|
-
@institution_name = 'Revolut'
|
19
|
-
|
20
|
-
super(options)
|
21
|
-
end
|
22
|
-
|
23
|
-
protected
|
24
|
-
|
25
|
-
def transformers(row)
|
26
|
-
date = extract_transaction_date(row).strftime('%d/%m/%Y')
|
27
|
-
payee = row[headers[:payee]]
|
28
|
-
unless row[headers[:debit]].nil?
|
29
|
-
debit = format('%<amount>.2f', amount: row[headers[:debit]])
|
30
|
-
end
|
31
|
-
unless row[headers[:credit]].nil?
|
32
|
-
credit = format('%<amount>.2f', amount: row[headers[:credit]])
|
33
|
-
end
|
34
|
-
|
35
|
-
ynab_row = [
|
36
|
-
date,
|
37
|
-
payee,
|
38
|
-
nil,
|
39
|
-
debit,
|
40
|
-
credit
|
41
|
-
]
|
42
|
-
|
43
|
-
logger.debug "Converted row: #{ynab_row}"
|
44
|
-
ynab_row
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def extract_header_names(row)
|
50
|
-
@headers[:transaction_date] ||= row.headers[0]
|
51
|
-
@headers[:payee] ||= row.headers[1]
|
52
|
-
@headers[:debit] ||= row.headers[2]
|
53
|
-
@headers[:credit] ||= row.headers[3]
|
54
|
-
end
|
55
|
-
|
56
|
-
def register_custom_converters
|
57
|
-
CSV::Converters[:amounts] = lambda { |s|
|
58
|
-
# Yes, amount come with a non breaking trailing space... Which is
|
59
|
-
# matched with \p{Zs} (c.f.
|
60
|
-
# https://ruby-doc.org/core-2.6/Regexp.html#class-Regexp-label-Character+Properties)
|
61
|
-
# Also, thousands separators can be non breaking spaces.
|
62
|
-
amount_regex = /^[\d'\.,\p{Zs}]+[\.,]\d{2}\p{Zs}$/
|
63
|
-
# narrow_nbsp = "\0xE2\0x80\0xAF"
|
64
|
-
narrow_nbsp = "\u{202F}"
|
65
|
-
readability_separators = "',. #{narrow_nbsp}"
|
66
|
-
|
67
|
-
if !s.nil? && s.match(amount_regex)
|
68
|
-
# This is a bit hacky because we don't have the luxury of Rails' i18n
|
69
|
-
# helpers. If we have an amount, strip all the separators in it, turn
|
70
|
-
# it to a float, and divide by 100 to get the right amount back
|
71
|
-
amount = s.delete(readability_separators).to_f / 100
|
72
|
-
logger.debug "Converted `#{s}' into amount `#{amount}'"
|
73
|
-
return amount
|
74
|
-
end
|
75
|
-
|
76
|
-
logger.debug "Not an amount, not parsing `#{s.inspect}'"
|
77
|
-
s
|
78
|
-
}
|
79
|
-
|
80
|
-
# rubocop:disable Style/AsciiComments
|
81
|
-
CSV::Converters[:transaction_dates] = lambda { |s|
|
82
|
-
begin
|
83
|
-
# Date.parse('6 decembre') is fine, but Date.parse('6 décembre') is
|
84
|
-
# an invalid date so we must remove diacritics before trying to parse
|
85
|
-
I18n.available_locales = [:en]
|
86
|
-
transliterated_s = I18n.transliterate s
|
87
|
-
logger.debug "Converted `#{s.inspect}' into date "\
|
88
|
-
"`#{Date.parse(transliterated_s)}'"
|
89
|
-
Date.parse(transliterated_s)
|
90
|
-
rescue StandardError
|
91
|
-
logger.debug "Not a date, not parsing #{s.inspect}"
|
92
|
-
s
|
93
|
-
end
|
94
|
-
}
|
95
|
-
# rubocop:enable Style/AsciiComments
|
96
|
-
end
|
97
|
-
|
98
|
-
def missing_transaction_date?(row)
|
99
|
-
# If It's missing a transaction date, it's most likely invalid
|
100
|
-
row[headers[:transaction_date]].nil?
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
@@ -1,115 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Processor
|
4
|
-
# Processes CSV files from UBS Personal Banking Switzerland
|
5
|
-
class UbsChequing < Processor::Base
|
6
|
-
# @option options [String] :file Path to the CSV file to process
|
7
|
-
def initialize(options)
|
8
|
-
register_custom_converters
|
9
|
-
@loader_options = {
|
10
|
-
col_sep: ';',
|
11
|
-
converters: %i[amounts transaction_dates],
|
12
|
-
quote_char: nil,
|
13
|
-
encoding: Encoding::UTF_8,
|
14
|
-
headers: true
|
15
|
-
}
|
16
|
-
@institution_name = 'UBS (Chequing)'
|
17
|
-
|
18
|
-
super(options)
|
19
|
-
end
|
20
|
-
|
21
|
-
protected
|
22
|
-
|
23
|
-
def transformers(row)
|
24
|
-
date = extract_transaction_date(row).strftime('%d/%m/%Y')
|
25
|
-
payee = transaction_payee(row)
|
26
|
-
unless row[headers[:debit]].nil?
|
27
|
-
debit = format('%<amount>.2f', amount: row[headers[:debit]])
|
28
|
-
end
|
29
|
-
unless row[headers[:credit]].nil?
|
30
|
-
credit = format('%<amount>.2f', amount: row[headers[:credit]])
|
31
|
-
end
|
32
|
-
|
33
|
-
converted_row = [
|
34
|
-
date,
|
35
|
-
payee,
|
36
|
-
nil,
|
37
|
-
debit,
|
38
|
-
credit
|
39
|
-
]
|
40
|
-
|
41
|
-
logger.debug "Converted row: #{converted_row}"
|
42
|
-
converted_row
|
43
|
-
end
|
44
|
-
|
45
|
-
def extract_transaction_date(row)
|
46
|
-
skip_row(row) if row[headers[:transaction_date]].nil?
|
47
|
-
row[headers[:transaction_date]]
|
48
|
-
end
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
def extract_header_names(row)
|
53
|
-
headers[:transaction_date] ||= row.headers[9]
|
54
|
-
headers[:payee_line_1] ||= row.headers[12]
|
55
|
-
headers[:payee_line_2] ||= row.headers[13]
|
56
|
-
headers[:payee_line_3] ||= row.headers[14]
|
57
|
-
headers[:debit] ||= row.headers[18]
|
58
|
-
headers[:credit] ||= row.headers[19]
|
59
|
-
end
|
60
|
-
|
61
|
-
def transaction_payee(row)
|
62
|
-
# Transaction description is spread over 3 columns.
|
63
|
-
# Moreover, UBS thought wise to append a bunch of junk information after
|
64
|
-
# the transaction details within the third description field. *Most* of
|
65
|
-
# this junk starts after the meaningful data and starts with ", OF",
|
66
|
-
# ", ON", ", ESR", ", QRR", two digits then five groups of five digits
|
67
|
-
# then ", TN" so we discard it; YNAB4 being unable to automatically
|
68
|
-
# categorize new transactions at the same store/payee because the payee
|
69
|
-
# always looks different (thanks to the variable nature of the appended
|
70
|
-
# junk).
|
71
|
-
# See `spec/fixtures/ubs_chequing/statement.csv` L2 and L18--22
|
72
|
-
|
73
|
-
# rubocop:disable Metrics/LineLength
|
74
|
-
junk_desc_regex = /, (O[FN]|ESR|QRR|\d{2} \d{5} \d{5} \d{5} \d{5} \d{5}, TN)/
|
75
|
-
# rubocop:enable Metrics/LineLength
|
76
|
-
|
77
|
-
[
|
78
|
-
row[headers[:payee_line_1]],
|
79
|
-
row[headers[:payee_line_2]],
|
80
|
-
row[headers[:payee_line_3]]
|
81
|
-
].join(' ').split(junk_desc_regex).first
|
82
|
-
end
|
83
|
-
|
84
|
-
def register_custom_converters
|
85
|
-
CSV::Converters[:amounts] = lambda { |s|
|
86
|
-
# Regex checks if string has only digits, apostrophes, and ends with a
|
87
|
-
# dot and two digits
|
88
|
-
amount_regex = /^[\d'?]+\.\d{2}$/
|
89
|
-
|
90
|
-
if !s.nil? && s.match(amount_regex)
|
91
|
-
amount = s.delete("'") .to_f
|
92
|
-
logger.debug "Converted `#{s}' into amount `#{amount}'"
|
93
|
-
return amount
|
94
|
-
end
|
95
|
-
|
96
|
-
logger.debug "Not an amount, not parsing `#{s.inspect}'"
|
97
|
-
s
|
98
|
-
}
|
99
|
-
|
100
|
-
CSV::Converters[:transaction_dates] = lambda { |s|
|
101
|
-
date_regex = /^\d{2}\.\d{2}\.\d{4}$/
|
102
|
-
|
103
|
-
if !s.nil? && s.match(date_regex)
|
104
|
-
parsed_date = Date.parse(s)
|
105
|
-
logger.debug "Converted `#{s.inspect}' into date "\
|
106
|
-
"`#{parsed_date}'"
|
107
|
-
parsed_date
|
108
|
-
else
|
109
|
-
logger.debug "Not a date, not parsing #{s.inspect}"
|
110
|
-
s
|
111
|
-
end
|
112
|
-
}
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
@@ -1,83 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Processor
|
4
|
-
# Processes CSV files from UBS Credit Cards Switzerland
|
5
|
-
class UbsCredit < Processor::Base
|
6
|
-
# @option options [String] :file Path to the CSV file to process
|
7
|
-
def initialize(options)
|
8
|
-
register_custom_converters
|
9
|
-
@loader_options = {
|
10
|
-
col_sep: ';',
|
11
|
-
converters: %i[amounts transaction_dates],
|
12
|
-
quote_char: nil,
|
13
|
-
encoding: "#{Encoding::ISO_8859_1}:#{Encoding::UTF_8}",
|
14
|
-
headers: true,
|
15
|
-
# CSV FTW, the first line in these files is not the headers but the
|
16
|
-
# separator specification
|
17
|
-
skip_lines: 'sep=;'
|
18
|
-
}
|
19
|
-
@institution_name = 'UBS (Credit cards)'
|
20
|
-
|
21
|
-
super(options)
|
22
|
-
end
|
23
|
-
|
24
|
-
protected
|
25
|
-
|
26
|
-
def transformers(row)
|
27
|
-
unless row[headers[:transaction_date]].nil?
|
28
|
-
date = row[headers[:transaction_date]].strftime('%d/%m/%Y')
|
29
|
-
end
|
30
|
-
payee = row[headers[:payee]]
|
31
|
-
unless row[headers[:debit]].nil?
|
32
|
-
debit = format('%<amount>.2f', amount: row[headers[:debit]])
|
33
|
-
end
|
34
|
-
unless row[headers[:credit]].nil?
|
35
|
-
credit = format('%<amount>.2f', amount: row[headers[:credit]])
|
36
|
-
end
|
37
|
-
|
38
|
-
converted_row = [date, payee, nil, debit, credit]
|
39
|
-
logger.debug "Converted row: #{converted_row}"
|
40
|
-
converted_row
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def extract_header_names(row)
|
46
|
-
headers[:transaction_date] ||= row.headers[3]
|
47
|
-
headers[:payee] ||= row.headers[4]
|
48
|
-
headers[:debit] ||= row.headers[10]
|
49
|
-
headers[:credit] ||= row.headers[11]
|
50
|
-
end
|
51
|
-
|
52
|
-
def register_custom_converters
|
53
|
-
CSV::Converters[:amounts] = lambda { |s|
|
54
|
-
# Regex checks if string has only digits, apostrophes, and ends with a
|
55
|
-
# dot and two digits
|
56
|
-
amount_regex = /^[\d'?]+(\.\d{2})$/
|
57
|
-
|
58
|
-
if !s.nil? && s.match(amount_regex)
|
59
|
-
amount = s.delete("'") .to_f
|
60
|
-
logger.debug "Converted `#{s}' into amount `#{amount}'"
|
61
|
-
return amount
|
62
|
-
end
|
63
|
-
|
64
|
-
logger.debug "Not an amount, not parsing `#{s.inspect}'"
|
65
|
-
s
|
66
|
-
}
|
67
|
-
|
68
|
-
CSV::Converters[:transaction_dates] = lambda { |s|
|
69
|
-
date_regex = /^\d{2}\.\d{2}\.\d{4}$/
|
70
|
-
|
71
|
-
if !s.nil? && s.match(date_regex)
|
72
|
-
parsed_date = Date.parse(s)
|
73
|
-
logger.debug "Converted `#{s.inspect}' into date "\
|
74
|
-
"`#{parsed_date}'"
|
75
|
-
parsed_date
|
76
|
-
else
|
77
|
-
logger.debug "Not a date, not parsing #{s.inspect}"
|
78
|
-
s
|
79
|
-
end
|
80
|
-
}
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|