mt940 0.6.6 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9687025fcee76507ec72ba177dbacfeedd3b755e
4
+ data.tar.gz: 2be9f9cf8bd116eabd1e5e28c1470809fe8f51ca
5
+ SHA512:
6
+ metadata.gz: eb88e8a1b47dee2efa2ea6cf008e92d3bf40a578eb834ddf469a00464b2b868a37f95b0ecfefad789bbae745196b53bf1cd3417621e277e2785477e263c4e0af
7
+ data.tar.gz: 65323779df20a4a8d4ce4f24994406026b70152b72ad5a31b3de95e3e778761bf6637245cd9aaf4ad67e0447ca70354845623a33b84fffc00ba788f97b0de606
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ mt940
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.0
data/.travis.yml CHANGED
@@ -1,10 +1,11 @@
1
1
  rvm:
2
+ - 2.1.0
3
+ - 2.0.0
2
4
  - 1.9.3
3
- - 1.9.2
4
- - 1.8.7
5
5
 
6
6
  branches:
7
7
  only:
8
8
  - master
9
+ - sepa
9
10
 
10
11
  script: "bundle exec rake test"
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ * 0.7.0
2
+ - No support for Ruby 1.8.7, default 2.1.0
3
+ - I was uncomfortable with using class methods, so I introduced the Mt940::Parser and refactored. See Readme
4
+ - Parser now works for mt940 files after SEPA was introduced (with the so called structured export format)
5
+
1
6
  * 0.6.6
2
7
  - Added currency support in transaction (contribution of Bob Forma)
3
8
 
data/Gemfile CHANGED
@@ -1,5 +1,10 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem 'rake', '0.8.7'
3
+ group :development, :test do
4
+ gem 'rake', '~> 10.1.1'
5
+ gem 'shoulda'
6
+ gem 'guard-test'
7
+ gem 'awesome_print'
8
+ end
4
9
 
5
10
  gemspec
data/Gemfile.lock CHANGED
@@ -1,18 +1,52 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mt940 (0.6.5)
4
+ mt940 (0.7.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
8
8
  specs:
9
- rake (0.8.7)
9
+ awesome_print (1.2.0)
10
+ celluloid (0.15.2)
11
+ timers (~> 1.1.0)
12
+ coderay (1.1.0)
13
+ ffi (1.9.3)
14
+ formatador (0.2.4)
15
+ guard (2.2.5)
16
+ formatador (>= 0.2.4)
17
+ listen (~> 2.1)
18
+ lumberjack (~> 1.0)
19
+ pry (>= 0.9.12)
20
+ thor (>= 0.18.1)
21
+ guard-test (2.0.3)
22
+ guard (~> 2.0)
23
+ test-unit (~> 2.2)
24
+ listen (2.4.0)
25
+ celluloid (>= 0.15.2)
26
+ rb-fsevent (>= 0.9.3)
27
+ rb-inotify (>= 0.9)
28
+ lumberjack (1.0.4)
29
+ method_source (0.8.2)
30
+ pry (0.9.12.4)
31
+ coderay (~> 1.0)
32
+ method_source (~> 0.8)
33
+ slop (~> 3.4)
34
+ rake (10.1.1)
35
+ rb-fsevent (0.9.3)
36
+ rb-inotify (0.9.3)
37
+ ffi (>= 0.5.0)
10
38
  shoulda (2.11.3)
39
+ slop (3.4.7)
40
+ test-unit (2.5.5)
41
+ thor (0.18.1)
42
+ timers (1.1.0)
11
43
 
12
44
  PLATFORMS
13
45
  ruby
14
46
 
15
47
  DEPENDENCIES
48
+ awesome_print
49
+ guard-test
16
50
  mt940!
17
- rake (= 0.8.7)
51
+ rake (~> 10.1.1)
18
52
  shoulda
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :test do
5
+ watch(%r{^lib/mt940/banks/(.+)\.rb$}) {|m| "test/mt940_#{m[1]}_test.rb" }
6
+ watch(%r{^test/.+_test\.rb$})
7
+ watch('test/test_helper.rb') { "test" }
8
+ watch('lib/mt940/base.rb') { "test" }
9
+ watch('lib/mt940/parser.rb') { "test" }
10
+ end
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Frank Oxener - Agile Dovadi BV
1
+ Copyright (c) 2014 Frank Oxener
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -5,9 +5,9 @@ MT940
5
5
  ![http://travis-ci.org/dovadi/mt940](https://secure.travis-ci.org/dovadi/mt940.png)
6
6
  </a>
7
7
 
8
- Basis parser for MT940 files, see [MT940](http://nl.wikipedia.org/wiki/MT940)
8
+ Basis parser for MT940 files, see [MT940](http://nl.wikipedia.org/wiki/MT940). This library is only meant for collection the transactions. No bank statements or balance (previous or new) is determined.
9
9
 
10
- The following Dutch banks are implemented:
10
+ The following Dutch banks are implemented with support for IBAN numbers:
11
11
 
12
12
  * ABN Amro
13
13
  * ING
@@ -21,7 +21,7 @@ With the file name as argument:
21
21
 
22
22
  file_name = '/Users/dovadi/Downloads/ing.940'
23
23
 
24
- @transactions = MT940:::Base.transactions(file_name)
24
+ @transactions = MT940::Parser.new(file_name).transactions
25
25
 
26
26
  or with the file itself:
27
27
 
@@ -29,19 +29,19 @@ or with the file itself:
29
29
 
30
30
  file = File.open(file_name)
31
31
 
32
- @transactions = MT940:::Base.transactions(file)
32
+ @transactions = MT940::Parser.new(file).transactions
33
33
 
34
34
 
35
35
  * Independent of the bank, a transaction always consists of:
36
36
 
37
- - accountnumber
38
- - bank (for example Ing, Rabobank or Unknown)
37
+ - accountnumber (BBAN or IBAN)
38
+ - bank (for example Ing, Rabobank etc )
39
39
  - date
40
40
  - amount (which is negative in case of a withdrawal)
41
41
  - description
42
42
  - contra account
43
43
 
44
- * With the Rabobank its owner is extracted as well.
44
+ (Pre sepa: with the Rabobank the contra_account owner is extracted as well)
45
45
 
46
46
  Running tests
47
47
  =============
@@ -64,5 +64,5 @@ Contributing to MT940
64
64
  Copyright
65
65
  ==========
66
66
 
67
- Copyright (c) 2012 Frank Oxener - Agile Dovadi BV. See LICENSE.txt for further details.
67
+ Copyright (c) 2014 Frank Oxener. See LICENSE.txt for further details.
68
68
 
@@ -0,0 +1,213 @@
1
+ MT940 voorbeeldbestanden 16-4-2013
2
+
3
+ Change History
4
+ - Corrected debit/credit indicator in 1.
5
+ - Corrected end balances
6
+ - Corrected CRLF (are not allowed mid label)
7
+ - Added REMI field
8
+ - 6a changed TRTP content, removed not used INF tag
9
+ - Removed final slashes where applicable
10
+
11
+
12
+ Inhoudsopgave:
13
+
14
+ 1. SCT overboeking credit
15
+ 2. SCT overboeking debet
16
+ 3. SCT batch
17
+ 4. SCT batch salaris
18
+ 5. SCT terugboeking (return en reject)
19
+ 6. SDD core batch incasso
20
+ 7. SDD core incasso (geincasseerde)
21
+ 8. SDD core incasso (cancellation, reversal, refund en refusal)
22
+ 9. SCT iDEAL
23
+ 10. SCT IBAN Acceptgiro
24
+
25
+ 1. SCT overboeking credit
26
+
27
+ ABNANL2A
28
+ 940
29
+ ABNANL2A
30
+ :20:ABN AMRO BANK NV
31
+ :25:123456789
32
+ :28:13501/1
33
+ :60F:C120511EUR5138,61
34
+ :61:1205120514C500,01N654NONREF
35
+ :86:/TRTP/SEPA OVERBOEKING/IBAN/FR12345678901234/BIC/GEFRADAM
36
+ /NAME/QASD JGRED/REMI/Dit zijn de omschrijvingsregels/EREF/NOTPRO
37
+ VIDED
38
+ :62F:C120514EUR5638,62
39
+
40
+ 2. SCT overboeking debet
41
+
42
+ ABNANL2A
43
+ 940
44
+ ABNANL2A
45
+ :20:ABN AMRO BANK NV
46
+ :25:123456789
47
+ :28:14901/1
48
+ :60F:D120525EUR1627,85
49
+ :61:1205280528D135,12N658NONREF
50
+ :86:/TRTP/SEPA OVERBOEKING/IBAN/BE12345678901234/BIC/KREDBEBB
51
+ /NAME/BELEKET/REMI/Omschrijving van de klant/EREF/05844183645
52
+ :62F:C120528EUR1492,73
53
+
54
+ 3. SCT batch
55
+
56
+ ABNANL2A
57
+ 940
58
+ ABNANL2A
59
+ :20:ABN AMRO BANK NV
60
+ :25:123456789
61
+ :28:14901/1
62
+ :60F:D120525EUR1627,85
63
+ :61:1205280528D135,12N655NONREF
64
+ :86:/TRTP/SEPA BATCH/PREF/109180/NRTX/162
65
+ :62F:C120528EUR1492,73
66
+
67
+ 4. SCT batch salaris
68
+
69
+ ABNANL2A
70
+ 940
71
+ ABNANL2A
72
+ :20:ABN AMRO BANK NV
73
+ :25:123456789
74
+ :28:14901/1
75
+ :60F:D120525EUR1627,85
76
+ :61:1205280528D135,12N652NONREF
77
+ :86:/TRTP/SEPA BATCH SALARIS/PREF/109180/NRTX/162
78
+ :62F:C120528EUR1492,73
79
+
80
+ 5. SCT terugboeking (return en reject)
81
+
82
+ ABNANL2A
83
+ 940
84
+ ABNANL2A
85
+ :20:ABN AMRO BANK NV
86
+ :25:123456789
87
+ :28:12901/1
88
+ :60F:C120507EUR5596,98
89
+ :61:1205080508C1,01N657NONREF
90
+ :86:/TRTP/SEPA TERUGBOEKING/IBAN/NL89ABNA1234567890/BIC/ABNANL2A
91
+ /NAME/M. GRSSUGE/RTRN/MS02/REMI/DIT IS EEN TESTBETALING OM TE KIJ
92
+ KEN OF DE BIC WEL GOED AFGEVANGEN WORDT/EREF/TESTBIC
93
+ :62F:C120508EUR5597,99
94
+
95
+ 6a. SDD core batch incasso
96
+
97
+ ABNANL2A
98
+ 940
99
+ ABNANL2A
100
+ :20:ABN AMRO BANK NV
101
+ :25:123456789
102
+ :28:13901/1
103
+ :60F:C120517EUR1945,64
104
+ :61:1112061206C0,05N247NONREF
105
+ :86:/TRTP/SEPA Incasso batch/PREF/109180-911/NRTX/162/PIND/BRUTO
106
+ :62F:C120518EUR1945,69
107
+
108
+ 6b. SDD B2B batch incasso
109
+
110
+ ABNANL2A
111
+ 940
112
+ ABNANL2A
113
+ :20:ABN AMRO BANK NV
114
+ :25:123456789
115
+ :28:13901/1
116
+ :60F:C120517EUR1945,64
117
+ :61:1112061206C0,05N249NONREF
118
+ :86:/TRTP/SEPA Incasso batch bedrijven/PREF/109180-911/NRTX/162
119
+ /PIND/BRUTO
120
+ :62F:C120518EUR1945,69
121
+
122
+ 7. SDD core incasso (geincasseerde)
123
+
124
+ ABNANL2A
125
+ 940
126
+ ABNANL2A
127
+ :20:ABN AMRO BANK NV
128
+ :25:123456789
129
+ :28:13901/1
130
+ :60F:C120517EUR1945,64
131
+ :61:1204240424D233,77N248NONREF
132
+ :86:/TRTP/SEPA Incasso algemeen doorlopend/CSID/NL13ZZZ0606057800
133
+ 00/NAME/Electriciteit leverancier BV/MARF/123456789XXmandaat
134
+ /REMI/Levering maand mei, zie nota 1234556. Uw klantnummer 123455
135
+ 666/IBAN/NL66ABNA1234567890/BIC/ABNANL2A/EREF/1234567X908303803
136
+ :62F:C120518EUR1711,87
137
+
138
+ 8a. SDD core incasso (Return)
139
+
140
+ ABNANL2A
141
+ 940
142
+ ABNANL2A
143
+ :20:ABN AMRO BANK NV
144
+ :25:123456789
145
+ :28:13901/1
146
+ :60F:C120517EUR1945,64
147
+ :61:1112081208D0,05N245NONREF
148
+ :86:/RTYP/SEPA Incasso Terugboeking/MARF/123456789XXmandaat/RTRN/
149
+ AC01/IBAN/ NL71ABNA1234567890/NAME/Debtor waarop geincasseeerd wo
150
+ rdt/REMI/Levering maand mei, zie nota 1234556. Uw klantnummer 123
151
+ 455666/EREF/1234567X908303803
152
+ :62F:C120518EUR1945,59
153
+
154
+ 8b. SDD core incasso (Reject)
155
+
156
+ ABNANL2A
157
+ 940
158
+ ABNANL2A
159
+ :20:ABN AMRO BANK NV
160
+ :25:123456789
161
+ :28:13901/1
162
+ :60F:C120517EUR1945,64
163
+ :61:1112081208D0,06N246NONREF
164
+ :86:/RTYP/SEPA Incasso niet uitgevoerd/MARF/123456789XXmandaat
165
+ /RTRN/MS02/IBAN/NL71ABNA1234567890/NAME/Debtor waarop geincasseee
166
+ rd wordt/REMI/Levering maand mei, zie nota 1234556. Uw klantnumme
167
+ r 123455666/EREF/1234567X908303803
168
+ :62F:C120518EUR1945,58
169
+
170
+ 9. SCT iDEAL
171
+
172
+ ABNANL2A
173
+ 940
174
+ ABNANL2A
175
+ :20:ABN AMRO BANK NV
176
+ :25:123456789
177
+ :28:13901/1
178
+ :60F:C120517EUR1945,64
179
+ :61:1209270927C0,01N944NONREF
180
+ :86:/TRTP/IDEAL/IBAN/NL73ANDL0123456789/BIC/ANDLNL20/NAME/NAAMXXX
181
+ 1/REMI/0030000322071306 18-06-12 21*09/EREF/INNDNL2U200001001348
182
+ 24001/ORDP//ID/1234567890AQCDEFGHIJ1234567890KLMNO
183
+ :62F:C120518EUR1945,65
184
+
185
+ 10a. SCT IBAN Acceptgiro (debet)
186
+
187
+ ABNANL2A
188
+ 940
189
+ ABNANL2A
190
+ :20:ABN AMRO BANK NV
191
+ :25:123456789
192
+ :28:13501/1
193
+ :60F:C120511EUR5138,61
194
+ :61:1205120514d1,01N946NONREF
195
+ :86:/TRTP/SEPA ACCEPTGIRO/IBAN/NL02ABNA0123456789/BIC/ABNANL2A
196
+ /NAME/Naam/REMI/Issuer: CUR Ref: 1234567812345678/EREF/NOTPROVIDE
197
+ D
198
+ :62F:C120514EUR5137,60
199
+
200
+ 10b. SCT IBAN Acceptgiro (credit)
201
+
202
+ ABNANL2A
203
+ 940
204
+ ABNANL2A
205
+ :20:ABN AMRO BANK NV
206
+ :25:123456789
207
+ :28:13501/1
208
+ :60F:C120511EUR5138,61
209
+ :61:1205120514C500,01N946NONREF
210
+ :86:/TRTP/SEPA ACCEPTGIRO/IBAN/NL02ABNA0123456789/BIC/ABNANL2A
211
+ /NAME/Naam/REMI/Issuer: CUR Ref: 1234567812345678/EREF/NOTPROVIDE
212
+ D
213
+ :62F:C120514EUR5538,62
Binary file
@@ -1,29 +1,31 @@
1
1
  class MT940::Abnamro < MT940::Base
2
2
 
3
- def self.determine_bank(*args)
4
- self if args[0].match(/ABNANL/)
5
- end
3
+ private
6
4
 
7
5
  def parse_tag_61
8
- if @line.match(/^:61:(\d{6})\d{4}(C|D)(\d+),(\d{0,2})/)
9
- type = $2 == 'D' ? -1 : 1
10
- @transaction = MT940::Transaction.new(:bank_account => @bank_account, :amount => type * ($3 + '.' + $4).to_f, :bank => @bank, :currency => @currency)
11
- @transaction.date = parse_date($1)
12
- @transactions << @transaction
13
- @tag86 = false
14
- end
6
+ super(/^:61:(\d{6})\d{4}(C|D)(\d+),(\d{0,2})/)
15
7
  end
16
8
 
17
- def parse_contra_account
18
- if @transaction
19
- if @transaction.description.match(/^(GIRO)\s+(\d+)(.+)/)
20
- @transaction.contra_account = $2.rjust(9, '000000000')
21
- @transaction.description = $3
22
- elsif @transaction.description.match(/^(\d{2}.\d{2}.\d{2}.\d{3})(.+)/)
23
- @transaction.description = $2
24
- @transaction.contra_account = $1.gsub('.','')
25
- end
9
+ def parse_line_before_sepa
10
+ @description = @line.gsub(/>\d{2}/,'').strip
11
+ if @description.match(/^(GIRO)\s+(\d+)(.+)/)
12
+ @contra_account = $2.rjust(9, '000000000')
13
+ @description = $3
14
+ elsif @description.match(/^(\d{2}.\d{2}.\d{2}.\d{3})(.+)/)
15
+ @description = $2
16
+ @contra_account = $1.gsub('.','')
26
17
  end
27
18
  end
28
19
 
20
+ def parse_line_after_sepa
21
+ hash = hashify_description(@line)
22
+ @description = hash['REMI']
23
+ @contra_account = hash['IBAN']
24
+ end
25
+
26
+ def sepa?
27
+ @line[0] == '/'
28
+ end
29
+
29
30
  end
31
+
@@ -1,14 +1,24 @@
1
1
  class MT940::Ing < MT940::Base
2
2
 
3
- def self.determine_bank(*args)
4
- self if args[0].match(/INGBNL/)
3
+ private
4
+
5
+ def parse_line_before_sepa
6
+ pattern = Regexp.new "(#{MT940::BBAN_PATTERN})(.+)"
7
+ if @line.match(pattern)
8
+ @description = $2.strip
9
+ @contra_account = $1[/[^0+]\d*/]
10
+ end
11
+ end
12
+
13
+ def parse_line_after_sepa
14
+ if @line.match(MT940::SEPA_PATTERN)
15
+ @contra_account = $2
16
+ @description = $4.strip
17
+ end
5
18
  end
6
19
 
7
- def parse_contra_account
8
- if @transaction && @transaction.description.match(/^\d(\d{9})(.+)/)
9
- @transaction.contra_account = $1
10
- @transaction.description = $2
11
- end
20
+ def sepa?
21
+ @line.match(MT940::SEPA_PATTERN)
12
22
  end
13
23
 
14
24
  end
@@ -1,28 +1,51 @@
1
1
  class MT940::Rabobank < MT940::Base
2
2
 
3
- def self.determine_bank(*args)
4
- self if args[0].match(/^:940:/)
3
+ private
4
+
5
+ def parse_tag_25
6
+ @line.gsub!('.','')
7
+ if @line.match(Regexp.new ":25:(#{MT940::IBAN_PATTERN})")
8
+ @bank_account = $1
9
+ @sepa = true
10
+ else
11
+ @bank_account = $1.gsub(/^0/,'') if @line.match(/^:\d{2}:[^\d]*(\d*)/)
12
+ end
5
13
  end
6
14
 
7
15
  def parse_tag_61
8
- if @line.match(/^:61:(\d{6})(C|D)(\d+),(\d{0,2})\w{4}\w{1}(\d{9}|NONREF)(.+)$/)
9
- type = $2 == 'D' ? -1 : 1
10
- @transaction = MT940::Transaction.new(:bank_account => @bank_account, :amount => type * ($3 + '.' + $4).to_f, :bank => @bank, :currency => @currency)
11
- @transaction.date = parse_date($1)
12
- @transaction.contra_account = $5.strip
13
- @transaction.contra_account_owner = $6.strip
14
- @transactions << @transaction
16
+ match = super(/^:61:(\d{6})(C|D)(\d+),(\d{0,2})\w{4}\w{1}(\d{9}|NONREF|EREF)(.*)$/)
17
+ if match
18
+ if @sepa
19
+ @transaction.contra_account = match[6].strip
20
+ else
21
+ @transaction.contra_account = match[5].strip
22
+ @transaction.contra_account_owner = match[6].strip
23
+ end
15
24
  end
16
25
  end
17
26
 
18
27
  def parse_tag_86
19
28
  if @line.match(/^:86:(.*)$/)
20
- if @transaction.description.nil?
21
- @transaction.description= $1.strip
22
- else
23
- @transaction.description += ' ' +$1.strip
24
- end
29
+ @line = $1.strip
30
+ @sepa ? determine_description_after_sepa : determine_description_before_sepa
31
+ @transaction.description = @description.strip
25
32
  end
26
33
  end
27
34
 
28
- end
35
+ def determine_description_before_sepa
36
+ if @description.nil?
37
+ @description = @line
38
+ else
39
+ @description += ' ' + @line
40
+ end
41
+ end
42
+
43
+ def determine_description_after_sepa
44
+ hash = hashify_description(@line)
45
+ @description = ''
46
+ @description += hash['NAME'] if hash['NAME']
47
+ @description += ' '
48
+ @description += hash['REMI'] if hash['REMI']
49
+ end
50
+
51
+ end
@@ -1,14 +1,45 @@
1
1
  class MT940::Triodos < MT940::Base
2
2
 
3
- def self.determine_bank(*args)
4
- self if args[0].match(/^:20:/) && args[1] && args[1].match(/^:25:TRIODOSBANK/)
3
+ private
4
+
5
+ def parse_tag_86
6
+ if @line.match(/^:86:000(.*)$/)
7
+ processed_description = hashify_description($1)
8
+
9
+ @transaction.contra_account = if sepa?(processed_description)
10
+ processed_description['21']
11
+ else
12
+ processed_description['10'][/[^0+]\d*/]
13
+ end
14
+
15
+ @transaction.description = extract_description(processed_description)
16
+ end
17
+ end
18
+
19
+ def bic_code?(text)
20
+ MT940::BIC_CODES.values.include?(text)
5
21
  end
6
22
 
7
- def parse_contra_account
8
- if @transaction && @transaction.description.match(/\d+(\d{9})$/)
9
- @transaction.contra_account = $1.rjust(9, '000000000')
10
- @transaction.description = ''
23
+ def extract_description(text)
24
+ identifier = sepa?(text) ? 22 : 20
25
+ description = ''
26
+ text.each do |k,v|
27
+ description += v if k.to_i >= identifier && k.to_i < 30
11
28
  end
29
+ description
30
+ end
31
+
32
+ def hashify_description(description)
33
+ hash = {}
34
+ description.split('>').compact.each do |slice|
35
+ next if slice.empty?
36
+ hash[slice[0..1]] = slice[2..-1]
37
+ end
38
+ hash
39
+ end
40
+
41
+ def sepa?(text)
42
+ text['10'] == '0000000000'
12
43
  end
13
44
 
14
- end
45
+ end