zilverline-mt940 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +15 -0
  2. data/.document +5 -0
  3. data/.gitignore +52 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG +56 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +26 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +75 -0
  10. data/Rakefile +9 -0
  11. data/lib/mt940.rb +10 -0
  12. data/lib/mt940/bank_statement.rb +13 -0
  13. data/lib/mt940/banks/abnamro.rb +76 -0
  14. data/lib/mt940/banks/ing.rb +84 -0
  15. data/lib/mt940/banks/rabobank.rb +770 -0
  16. data/lib/mt940/banks/triodos.rb +20 -0
  17. data/lib/mt940/base.rb +165 -0
  18. data/lib/mt940/structured_format.rb +16 -0
  19. data/lib/mt940/transaction.rb +23 -0
  20. data/lib/mt940/version.rb +3 -0
  21. data/mt940.gemspec +32 -0
  22. data/spec/fixtures/abnamro.txt +41 -0
  23. data/spec/fixtures/abnamro_structured.txt +54 -0
  24. data/spec/fixtures/ing.txt +24 -0
  25. data/spec/fixtures/ing_structured.txt +31 -0
  26. data/spec/fixtures/rabobank.txt +140 -0
  27. data/spec/fixtures/rabobank_mt940_structured.txt +48 -0
  28. data/spec/fixtures/rabobank_mt940_structured_dutch_tax.txt +10 -0
  29. data/spec/fixtures/rabobank_mt940_structured_multi_line.txt +14 -0
  30. data/spec/fixtures/rabobank_mt940_structured_savings_account.txt +11 -0
  31. data/spec/fixtures/rabobank_mt940_structured_to_savings_account.txt +10 -0
  32. data/spec/fixtures/rabobank_with_debet_previous_balance.txt +6 -0
  33. data/spec/fixtures/triodos.txt +14 -0
  34. data/spec/fixtures/two_accounts.txt +29 -0
  35. data/spec/fixtures/unknown.txt +22 -0
  36. data/spec/mt940_abnamro_spec.rb +244 -0
  37. data/spec/mt940_base_spec.rb +48 -0
  38. data/spec/mt940_ing_spec.rb +227 -0
  39. data/spec/mt940_rabobank_spec.rb +376 -0
  40. data/spec/mt940_triodos_spec.rb +58 -0
  41. data/spec/mt940_two_accounts_spec.rb +49 -0
  42. data/spec/spec_helper.rb +4 -0
  43. metadata +137 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ M2VjYjdlOGZlYzJkMDZmOWIzZTkzODkwNjc0YThmYjg0NTg0MDdmOA==
5
+ data.tar.gz: !binary |-
6
+ Mjg3Mzg5YmYzMTE4NDkzYWIwYTBhMWVkMDlhM2U1M2UyYjg5ZTY1ZQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YmRlMTM0ZTI5MzdmYmE3NGIwZTBmNGQwOTI1YmU1NzJlY2I1ZDNkNTk0YWY2
10
+ NWZhNzYzYWU1ZTFmNDJmNTE0NzRhZTgzN2M1NGIxNTNiN2Q1MjkzMzM5MzVj
11
+ ZjRjMDBlYjRiM2Y4M2IzY2Y5M2Q1OWVmYTFmMGQzNTMyMjI3NTM=
12
+ data.tar.gz: !binary |-
13
+ NWU5Njk2YWU4N2E1ZTQ0YjUzZDVhZjkzY2ViNTEyOTQ0ZTE5MGQ0MjBkOWNi
14
+ NzRmOTNmNGZiODQ0MjZiMDE4ZDE4YzQ3N2M2MDYyYjc2OWY5YjAzNGJlMzUy
15
+ YzdmMGFmYzg2NjRiOTAwNDFkODkxODU4NTU2ODJmYjBhNTQzM2M=
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,52 @@
1
+ # rcov generated
2
+ coverage
3
+
4
+ # rdoc generated
5
+ rdoc
6
+
7
+ # yard generated
8
+ doc
9
+ .yardoc
10
+
11
+ # bundler
12
+ .bundle
13
+
14
+ # jeweler generated
15
+ pkg
16
+
17
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
18
+ #
19
+ # * Create a file at ~/.gitignore
20
+ # * Include files you want ignored
21
+ # * Run: git config --global core.excludesfile ~/.gitignore
22
+ #
23
+ # After doing this, these files will be ignored in all your git projects,
24
+ # saving you from having to 'pollute' every project you touch with them
25
+ #
26
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
27
+ #
28
+ # For MacOS:
29
+ #
30
+ #.DS_Store
31
+
32
+ # For TextMate
33
+ #*.tmproj
34
+ #tmtags
35
+
36
+ # For emacs:
37
+ #*~
38
+ #\#*
39
+ #.\#*
40
+
41
+ # For vim:
42
+ #*.swp
43
+
44
+ # For redcar:
45
+ #.redcar
46
+
47
+ # For rubinius:
48
+ #*.rbc
49
+
50
+ # rvmrc
51
+ .rvmrc
52
+ .idea/
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p385
data/CHANGELOG ADDED
@@ -0,0 +1,56 @@
1
+ * 0.6.6
2
+ - Added currency support in transaction (contribution of Bob Forma)
3
+
4
+ * 0.6.5
5
+
6
+ - Use bundler for gem building and release
7
+ - Add travis configuration
8
+
9
+ * 0.6.4
10
+
11
+ - Fix for multiline description for Rabobank
12
+
13
+ * 0.6.3
14
+
15
+ - include the updated Changelog and README as well
16
+
17
+ * 0.6.2 (Was never released)
18
+
19
+ - Parse a NONREF contra account for the rabobank correctly
20
+
21
+ * 0.6.1 (Was never released)
22
+
23
+ - Extracting contra account for ING, AbnAmro and Triodos as well
24
+ - Standardized all account numbers to exactly 9 digits
25
+
26
+ * 0.6.0 (Was never released)
27
+
28
+ * 0.5.1
29
+
30
+ - Delegated determination of bank to corresponding subclass
31
+
32
+ * 0.5.0
33
+
34
+ - Added bank to a transaction
35
+
36
+ * 0.4.1
37
+
38
+ - Handle can be a Tempfile as well
39
+
40
+ * 0.4.0
41
+
42
+ - Also handle to MT940 file possible
43
+
44
+ * 0.3.0
45
+
46
+ - Added a date to a transaction
47
+ - Change transaction attribute name of contra_account_name into contra_account_owner
48
+
49
+ * 0.2.0
50
+
51
+ - General parse method refactored and broke down in individual parse methods per tag
52
+ - Automatic determination of bank on the base of the first line implemented
53
+
54
+ * 0.1.0
55
+
56
+ Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in mt940.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,26 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ zilverline-mt940 (1.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.3)
10
+ rake (10.1.0)
11
+ rspec (2.12.0)
12
+ rspec-core (~> 2.12.0)
13
+ rspec-expectations (~> 2.12.0)
14
+ rspec-mocks (~> 2.12.0)
15
+ rspec-core (2.12.0)
16
+ rspec-expectations (2.12.0)
17
+ diff-lcs (~> 1.1.3)
18
+ rspec-mocks (2.12.0)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ rake
25
+ rspec
26
+ zilverline-mt940!
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Frank Oxener - Agile Dovadi BV
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ MT940
2
+ ======
3
+
4
+ Full parser for MT940 files, see [MT940](http://nl.wikipedia.org/wiki/MT940). This is based on
5
+ the original gem of [Frank Oxener - Agile Dovadi BV](http://github.com/dovadi/mt940) but completely redesigned and extended.
6
+
7
+ The following Dutch banks are implemented:
8
+
9
+ * ABN Amro
10
+ * ING
11
+ * Rabobank
12
+ * Triodos
13
+
14
+ Usage
15
+ =====
16
+
17
+ With the file name as argument:
18
+
19
+ file_name = '~/Downloads/ing.940'
20
+
21
+ @parse_result = MT940::Base.parse_mt940(file_name)
22
+
23
+ or with the file itself:
24
+
25
+ file_name = '~/Downloads/ing.940'
26
+
27
+ file = File.open(file_name)
28
+
29
+ @parse_result = MT940::Base.parse_mt940(file)
30
+
31
+ after parsing:
32
+
33
+ @parse_result.each do |account_number, bank_statements|
34
+ puts "Account number #{account_number} has #{bank_statements.size} bank statements"
35
+ bank_statements.each do |bank_statement|
36
+ puts "Bank statement has balance of #{bank_statement.previous_balance.amount} at date #{bank_statement.previous_balance.date}"
37
+ bank_statement.transactions.each do |transaction|
38
+ # do something with transaction
39
+ # ...
40
+ end
41
+ puts "Bank statement has new balance of #{bank_statement.new_balance.amount} at date #{bank_statement.new_balance.date}"
42
+ end
43
+ end
44
+
45
+ * Independent of the bank
46
+
47
+ - a parse_result consists of:
48
+
49
+ - a map with account numbers as key and a list of BankStatements (http://en.wikipedia.org/wiki/Bank_statement)
50
+ - A BankStatement is a summary of financial transaction in a certain period of time.
51
+ - It is a Struct
52
+ - It contains a previous_balance (Balance) and a new_balance (Balance)
53
+ - It has a list of Transactions
54
+ - a transaction always consists of:
55
+ - accountnumber
56
+ - bank (for example Ing, Rabobank or Unknown)
57
+ - date
58
+ - amount (which is negative in case of a withdrawal)
59
+ - description
60
+ - contra account
61
+
62
+ * With the Rabobank its owner is extracted as well.
63
+
64
+ Running tests
65
+ =============
66
+
67
+ > bundle install
68
+
69
+ > bundle exec rake spec
70
+
71
+ Copyright
72
+ ==========
73
+
74
+ Copyright (c) 2012 Frank Oxener - Agile Dovadi BV. See LICENSE.txt for further details.
75
+
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+
data/lib/mt940.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'tempfile'
2
+ require 'date'
3
+ require_relative 'mt940/transaction'
4
+ require_relative 'mt940/bank_statement'
5
+ require_relative 'mt940/base'
6
+ require_relative 'mt940/structured_format'
7
+ require_relative 'mt940/banks/ing'
8
+ require_relative 'mt940/banks/rabobank'
9
+ require_relative 'mt940/banks/abnamro'
10
+ require_relative 'mt940/banks/triodos'
@@ -0,0 +1,13 @@
1
+ module MT940
2
+ ##
3
+ # A Bankstatement contains a single or multiple Transaction's.
4
+ # It is the equivalent of an actual real life gold old paper bank statement
5
+ # as we used to get them via post in the old days.
6
+ #
7
+ BankStatement = Struct.new(:transactions, :bank_account, :bank_account_iban, :page_number, :previous_balance, :new_balance)
8
+
9
+ ##
10
+ # A Balance describes the amount of money you have on your bank account
11
+ # at a certain moment in time.
12
+ Balance = Struct.new(:amount, :date, :currency)
13
+ end
@@ -0,0 +1,76 @@
1
+ class MT940::Abnamro < MT940::Base
2
+ include MT940::StructuredFormat
3
+
4
+ def self.determine_bank(*args)
5
+ self if args[0].match(/ABNANL/)
6
+ end
7
+
8
+ def mt_940_start_line?(line)
9
+ super || line.match(/ABNANL/)
10
+ end
11
+
12
+ def parse_tag_61
13
+ if @line.match(/^:61:(\d{6})\d{4}(C|D)(\d+),(\d{0,2})/)
14
+ type = $2 == 'D' ? -1 : 1
15
+ @transaction = MT940::Transaction.new(:bank_account => @bank_account, :amount => type * ($3 + '.' + $4).to_f, :bank => @bank, :currency => @currency)
16
+ @transaction.date = parse_date($1)
17
+ @bank_statement.transactions << @transaction
18
+ @tag86 = false
19
+ end
20
+ end
21
+
22
+ def parse_contra_account
23
+ if @transaction
24
+
25
+ case @transaction.description
26
+ when /^(GIRO)\s+(\d+)(.+)/
27
+ @transaction.contra_account = $2.rjust(9, '000000000')
28
+ @transaction.description = $3
29
+ when /^(\d{2}.\d{2}.\d{2}.\d{3})(.+)/
30
+ @transaction.description = $2.strip
31
+ @transaction.contra_account = $1.gsub('.', '')
32
+ when /\/TRTP\/SEPA OVERBOEKING/
33
+ description_parts = @line[4..-1].split('/')
34
+ @transaction.contra_account_iban = parse_description_after_tag description_parts, "IBAN"
35
+ @transaction.contra_account = iban_to_account @transaction.contra_account_iban
36
+ @transaction.contra_account_owner = parse_description_after_tag description_parts, "NAME"
37
+ @transaction.description = parse_description_after_tag description_parts, "REMI"
38
+ when /SEPA IDEAL/
39
+ read_all_description_lines!
40
+ full_description = @transaction.description
41
+ if full_description.match /OMSCHRIJVING\:(.+)?/
42
+ @transaction.description = $1.strip
43
+ end
44
+ if full_description.match /IBAN\:(.+)?BIC\:/
45
+ @transaction.contra_account_iban = $1.strip
46
+ @transaction.contra_account = iban_to_account @transaction.contra_account_iban
47
+ end
48
+ if full_description.match /NAAM\:(.+)?OMSCHRIJVING\:/
49
+ @transaction.contra_account_owner = $1.strip
50
+ end
51
+ when /SEPA ACCEPTGIROBETALING/
52
+ read_all_description_lines!
53
+ full_description = @transaction.description
54
+ if full_description.match /(BETALINGSKENM\.\:.+)/
55
+ @transaction.description = $1.strip
56
+ end
57
+ if full_description.match /IBAN\:(.+)?BIC\:/
58
+ @transaction.contra_account_iban = $1.strip
59
+ @transaction.contra_account = iban_to_account @transaction.contra_account_iban
60
+ end
61
+ if full_description.match /NAAM\:(.+)?BETALINGSKENM\.\:/
62
+ @transaction.contra_account_owner = $1.strip
63
+ end
64
+ else
65
+ @skip_parse_line = false
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def iban_to_account(iban)
73
+ !iban.nil? ? iban.split(//).last(10).join.gsub(/^0+/, '') : nil
74
+ end
75
+
76
+ end
@@ -0,0 +1,84 @@
1
+ class MT940::Ing < MT940::Base
2
+
3
+ IBAN_BIC_R = /^([a-zA-Z]{2}[0-9]{2}[a-zA-Z0-9]{0,30})(?:\s)([a-zA-Z0-9]{8,11})(?:\s)(.*)/
4
+ CONTRA_ACCOUNT_DESCRIPTION_R = /^(.*)(?:\s)(?:NOTPROVIDED)(?:\s)(.*)/
5
+
6
+
7
+ def self.determine_bank(*args)
8
+ self if args[0].match(/INGBNL/)
9
+ end
10
+
11
+ def parse_tag_61
12
+
13
+ @is_structured_format = @line.match /EREF|PREF|MARF|\d{16}/
14
+
15
+ if @line.match(/^:61:(\d{6})(C|D)(\d+),(\d{0,2})N(\S+)/)
16
+ sign = $2 == 'D' ? -1 : 1
17
+ @transaction = MT940::Transaction.new(:bank_account => @bank_account, :amount => sign * ($3 + '.' + $4).to_f, :bank => @bank, :currency => @currency)
18
+ @transaction.type = human_readable_type($5.strip)
19
+ @transaction.date = parse_date($1)
20
+ @bank_statement.transactions << @transaction
21
+ @tag86 = false
22
+ else
23
+ raise @line
24
+ end
25
+ end
26
+
27
+ def parse_tag_62F
28
+ super
29
+ @tag86 = true
30
+ end
31
+
32
+ def parse_contra_account
33
+ if @is_structured_format && @transaction && @transaction.description.match(IBAN_BIC_R)
34
+ parse_structured_description $1, $3
35
+ elsif @transaction && @transaction.description.match(/([P|\d]\d{9})?(.+)/)
36
+ parse_description $1, $2
37
+ end
38
+ end
39
+
40
+ def human_readable_type(type)
41
+ ING_MAPPING[type.strip] || type.strip
42
+ end
43
+
44
+ ING_MAPPING = {}
45
+ ING_MAPPING["AC"]= "Acceptgiro"
46
+ ING_MAPPING["BA"]= "Betaalautomaattransactie"
47
+ ING_MAPPING["CH"]= "Cheque"
48
+ ING_MAPPING["DV"]= "Diversen"
49
+ ING_MAPPING["FL"]= "Filiaalboeking, concernboeking"
50
+ ING_MAPPING["GF"]= "Telefonisch bankieren"
51
+ ING_MAPPING["GM"]= "Geldautomaat"
52
+ ING_MAPPING["GT"]= "Internetbankieren"
53
+ ING_MAPPING["IC"]= "Incasso"
54
+ ING_MAPPING["OV"]= "Overschrijving"
55
+ ING_MAPPING["PK"]= "Opname kantoor"
56
+ ING_MAPPING["PO"]= "Periodieke overschrijving"
57
+ ING_MAPPING["ST"]= "ST Storting (eigen rekening of derde)"
58
+ ING_MAPPING["VZ"]= "Verzamelbetaling"
59
+ ING_MAPPING["Code"]= "Toelichting"
60
+ ING_MAPPING["CHK"]= "Cheque"
61
+ ING_MAPPING["TRF"]= "Overboeking buitenland"
62
+
63
+ private
64
+ # introduced by IBAN
65
+ def parse_structured_description(iban, description)
66
+ @transaction.contra_account_iban=iban
67
+ @transaction.description=description
68
+ @transaction.contra_account=@transaction.contra_account_iban[8..-1].sub(/^0+/, '') if @transaction.contra_account_iban.match /^NL/
69
+ if @transaction.description.match CONTRA_ACCOUNT_DESCRIPTION_R
70
+ @transaction.contra_account_owner=$1
71
+ @transaction.description=$2
72
+ end
73
+ end
74
+
75
+ def parse_description(account_number, description)
76
+ @transaction.description = description.strip
77
+ number = account_number
78
+ unless number.nil?
79
+ @transaction.contra_account = number.gsub(/\D/, '').gsub(/^0+/, '')
80
+ else
81
+ @transaction.contra_account = "NONREF"
82
+ end
83
+ end
84
+ end