zilverline-mt940 1.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 (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