cmxl 1.5.0 → 2.1
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/.github/workflows/ci.yml +31 -0
- data/.github/workflows/release.yml +26 -0
- data/.rspec +1 -0
- data/CHANGELOG.mdown +11 -0
- data/README.md +7 -7
- data/cmxl.gemspec +2 -2
- data/lib/cmxl/field.rb +1 -1
- data/lib/cmxl/fields/transaction.rb +43 -8
- data/lib/cmxl/statement.rb +2 -2
- data/lib/cmxl/version.rb +1 -1
- data/lib/cmxl.rb +8 -4
- data/spec/fields/transaction_spec.rb +82 -4
- data/spec/fixtures/mt940-abnamro.txt +44 -0
- data/spec/fixtures/mt940-handelsbank.txt +16 -0
- data/spec/fixtures/mt940-windows-line-breaks.txt +16 -0
- data/spec/mt940_parsing_spec.rb +111 -0
- data/spec/statement_spec.rb +92 -2
- metadata +25 -20
- data/.travis.yml +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c6114278dc458c8f3890f6f04e72a33915c98f235fe0694ba8d7b02cc5f394d6
|
4
|
+
data.tar.gz: d47ca765f53acf8f86d9c51b25123975174fc83c86cc68b834f069606ecd8cd4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ac2f348609babd4cca9df8da0412f965057fed77d210e155f076c489d407541effd6da7012b00ff48f642baec2ae96b028bad0720f08ff50c4f2327d34dc7bf
|
7
|
+
data.tar.gz: cfae7ffcf5ffe4ee50b2afac7828f712a9511e5e1da6493a17a7d94681094fee7729b2687ee5a9fdc9bad7e031778841ed8cda3db44c6741a6810c54f736d25a
|
@@ -0,0 +1,31 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [main]
|
6
|
+
pull_request:
|
7
|
+
branches: [main]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
|
13
|
+
strategy:
|
14
|
+
matrix:
|
15
|
+
ruby-version:
|
16
|
+
- 3.4
|
17
|
+
- 3.3
|
18
|
+
- 3.2
|
19
|
+
- 3.1
|
20
|
+
- "3.0"
|
21
|
+
- jruby-9.4
|
22
|
+
|
23
|
+
steps:
|
24
|
+
- uses: actions/checkout@v4
|
25
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
26
|
+
uses: ruby/setup-ruby@v1
|
27
|
+
with:
|
28
|
+
ruby-version: ${{ matrix.ruby-version }}
|
29
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
30
|
+
- name: Run tests
|
31
|
+
run: bundle exec rake
|
@@ -0,0 +1,26 @@
|
|
1
|
+
name: Release
|
2
|
+
|
3
|
+
on:
|
4
|
+
release:
|
5
|
+
types: [published]
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
push:
|
9
|
+
name: Push gem to RubyGems.org
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
|
12
|
+
permissions:
|
13
|
+
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
14
|
+
contents: write # IMPORTANT: this permission is required for `rake release` to push the release tag
|
15
|
+
|
16
|
+
steps:
|
17
|
+
# Set up
|
18
|
+
- uses: actions/checkout@v4
|
19
|
+
- name: Set up Ruby
|
20
|
+
uses: ruby/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
bundler-cache: true
|
23
|
+
ruby-version: ruby
|
24
|
+
|
25
|
+
# Release
|
26
|
+
- uses: rubygems/release-gem@v1
|
data/.rspec
CHANGED
data/CHANGELOG.mdown
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# NEXT release
|
2
2
|
|
3
|
+
# 2.1
|
4
|
+
- `[REFACTOR]` improve file parser to work with windows line breaks and any known header formats
|
5
|
+
- `[BUGFIX]` fix `strip_header` making it work on statements without headers
|
6
|
+
- `[HOUSEKEEPING]` use `main` instead of `master` as default branch
|
7
|
+
- `[ENHANCEMENT]` add support for ruby `3.4`
|
8
|
+
|
9
|
+
# 2.0
|
10
|
+
- `[REFACTOR]` **DEPRECATED:** `storno?` and related methods `storno_credit`, `storno_debit`. Use `reversal?` and related methods `reversal_credit?`, `reversal_debit?` instead
|
11
|
+
- `[BUGFIX]` **DEPRECATED:** `funds_code` method returns the `credit_debit_indicator` from the SWIFT definition. Therefore the method is deprecated in favor of `credit_debit_indicator` method
|
12
|
+
- `[HOUSEKEEPING]` [Replace Travis CI with github actions](https://github.com/railslove/cmxl/pull/57)
|
13
|
+
|
3
14
|
# 1.5.0
|
4
15
|
|
5
16
|
- `[BUGFIX]` fix potential bug when generation_date is not provided in field 20 and 13 (issue: [#35](https://github.com/railslove/cmxl/issues/35) PR: [#36](https://github.com/railslove/cmxl/pull/36))
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
[](
|
1
|
+
[](https://github.com/railslove/cmxl/actions/workflows/ci.yml)
|
2
|
+
[](https://rubygems.org/gems/cmxl)
|
3
3
|
|
4
4
|
# Cmxl - your friendly ruby MT940 parser
|
5
5
|
|
@@ -16,7 +16,7 @@ For more information have a look at the different [SWIFT message types](http://e
|
|
16
16
|
|
17
17
|
At some point in the future MT940 file should be exchanged with newer XML documents - but banking institutions are slow, so MT940 will stick around for a while.
|
18
18
|
|
19
|
-
##
|
19
|
+
## Requirements
|
20
20
|
|
21
21
|
Cmxl is a pure ruby parser and has no dependency on native extensions.
|
22
22
|
|
@@ -62,13 +62,13 @@ statements.each do |s|
|
|
62
62
|
puts s.generation_date
|
63
63
|
puts s.opening_balance.amount
|
64
64
|
puts s.closing_balance.amount
|
65
|
-
puts s.sha # SHA of the statement source - could be used as an identifier (see: https://github.com/railslove/cmxl/blob/
|
65
|
+
puts s.sha # SHA of the statement source - could be used as an identifier (see: https://github.com/railslove/cmxl/blob/main/lib/cmxl/statement.rb#L49-L55)
|
66
66
|
|
67
67
|
s.transactions.each do |t|
|
68
68
|
puts t.information
|
69
69
|
puts t.description
|
70
70
|
puts t.entry_date
|
71
|
-
puts t.
|
71
|
+
puts t.credit_debit_indicator
|
72
72
|
puts t.credit?
|
73
73
|
puts t.debit?
|
74
74
|
puts t.sign # -1 if it's a debit; 1 if it's a credit
|
@@ -96,7 +96,7 @@ If that fails, try to modify the file before you pass it to the parser - and ple
|
|
96
96
|
### MT940 SWIFT header data
|
97
97
|
|
98
98
|
Cmxl currently does not support parsing of the SWIFT headers (like {1:F01AXISINBBA ....)
|
99
|
-
If your file comes with these headers try the `strip_headers` configuration option to strip data
|
99
|
+
If your file comes with these headers try the `strip_headers` configuration option to strip data except the actual MT940 fields.
|
100
100
|
|
101
101
|
```ruby
|
102
102
|
Cmxl.config[:strip_headers] = true
|
@@ -187,6 +187,6 @@ other parsers:
|
|
187
187
|
|
188
188
|
---
|
189
189
|
built with love by [Railslove](http://railslove.com) and some [amazing people](https://github.com/railslove/cmxl/graphs/contributors).
|
190
|
-
Released under the MIT-
|
190
|
+
Released under the MIT-License.
|
191
191
|
|
192
192
|
Railslove builds FinTech products, if you need support for your project we are happy to help. Please contact us at team@railslove.com.
|
data/cmxl.gemspec
CHANGED
@@ -19,11 +19,11 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
|
22
|
+
spec.add_dependency 'rchardet'
|
23
|
+
|
22
24
|
spec.add_development_dependency 'bundler'
|
23
25
|
spec.add_development_dependency 'pry'
|
24
26
|
spec.add_development_dependency 'rake'
|
25
27
|
spec.add_development_dependency 'rspec', '~>3.0'
|
26
28
|
spec.add_development_dependency 'simplecov'
|
27
|
-
|
28
|
-
spec.add_dependency 'rchardet19'
|
29
29
|
end
|
data/lib/cmxl/field.rb
CHANGED
@@ -13,7 +13,7 @@ module Cmxl
|
|
13
13
|
|
14
14
|
# The parser class variable is the registry of all available parser.
|
15
15
|
# It is a hash with the tag (MT940 field number/tag) as key and the class as value
|
16
|
-
# When parsing a
|
16
|
+
# When parsing a statement line we look for a matching entry or use the Unknown class as default
|
17
17
|
@@parsers = {}
|
18
18
|
@@parsers.default = Unknown
|
19
19
|
def self.parsers
|
@@ -2,7 +2,7 @@ module Cmxl
|
|
2
2
|
module Fields
|
3
3
|
class Transaction < Field
|
4
4
|
self.tag = 61
|
5
|
-
self.parser = %r{^(?<date>\d{6})(?<entry_date>\d{4})?(?<
|
5
|
+
self.parser = %r{^(?<date>\d{6})(?<entry_date>\d{4})?(?<credit_debit_indicator>D|C|RD|RC|ED|EC)(?<currency_letter>[a-zA-Z])?(?<amount>\d{1,12},\d{0,2})(?<swift_code>(?:N|F|S).{3})(?<reference>NONREF|(.(?!\/\/)){,16}([^\/]){,1})((?:\/\/)(?<bank_reference>[^\n]{,16}))?((?:\n)(?<supplementary>.{,34}))?$}
|
6
6
|
|
7
7
|
attr_accessor :details
|
8
8
|
|
@@ -15,31 +15,63 @@ module Cmxl
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def credit?
|
18
|
-
|
18
|
+
credit_debit_indicator.include?('C')
|
19
19
|
end
|
20
20
|
|
21
21
|
def debit?
|
22
|
-
|
22
|
+
credit_debit_indicator.include?('D')
|
23
23
|
end
|
24
24
|
|
25
25
|
def storno_credit?
|
26
|
+
warn "[DEPRECATION] `storno_credit?` is deprecated. Please use `reversal_credit?` instead. It will be removed in version 3.0."
|
27
|
+
reversal_credit?
|
28
|
+
end
|
29
|
+
|
30
|
+
def reversal_credit?
|
26
31
|
credit? && storno?
|
27
32
|
end
|
28
33
|
|
29
34
|
def storno_debit?
|
35
|
+
warn "[DEPRECATION] `storno_debit?` is deprecated. Please use `reversal_debit?` instead. It will be removed in version 3.0."
|
36
|
+
reversal_debit?
|
37
|
+
end
|
38
|
+
|
39
|
+
def reversal_debit?
|
30
40
|
debit? && storno?
|
31
41
|
end
|
32
42
|
|
33
43
|
def storno?
|
34
|
-
|
44
|
+
warn "[DEPRECATION] `storno?` is deprecated. Please use `reversal?` instead. It will be removed in version 3.0."
|
45
|
+
reversal?
|
46
|
+
end
|
47
|
+
|
48
|
+
def reversal?
|
49
|
+
credit_debit_indicator.include?('R')
|
50
|
+
end
|
51
|
+
|
52
|
+
def expected_credit?
|
53
|
+
credit? && expected?
|
54
|
+
end
|
55
|
+
|
56
|
+
def expected_debit?
|
57
|
+
debit? && expected?
|
58
|
+
end
|
59
|
+
|
60
|
+
def expected?
|
61
|
+
credit_debit_indicator.include?('E')
|
62
|
+
end
|
63
|
+
|
64
|
+
def credit_debit_indicator
|
65
|
+
data['credit_debit_indicator'].to_s
|
35
66
|
end
|
36
67
|
|
37
68
|
def funds_code
|
38
|
-
|
69
|
+
warn "[DEPRECATION] `funds_code` is deprecated. Please use `credit_debit_indicator` instead. It will be removed in version 3.0."
|
70
|
+
data['credit_debit_indicator'].to_s
|
39
71
|
end
|
40
72
|
|
41
73
|
def storno_flag
|
42
|
-
|
74
|
+
reversal? ? 'R' : ''
|
43
75
|
end
|
44
76
|
|
45
77
|
def sign
|
@@ -140,8 +172,11 @@ module Cmxl
|
|
140
172
|
'sign' => sign,
|
141
173
|
'debit' => debit?,
|
142
174
|
'credit' => credit?,
|
143
|
-
'storno' =>
|
144
|
-
'
|
175
|
+
'storno' => reversal?,
|
176
|
+
'reversal' => reversal?,
|
177
|
+
'expected' => expected?,
|
178
|
+
'funds_code' => credit_debit_indicator,
|
179
|
+
'credit_debit_indicator' => credit_debit_indicator,
|
145
180
|
'swift_code' => swift_code,
|
146
181
|
'reference' => reference,
|
147
182
|
'bank_reference' => bank_reference,
|
data/lib/cmxl/statement.rb
CHANGED
@@ -41,8 +41,8 @@ module Cmxl
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def strip_headers!
|
44
|
-
source.gsub!(/\A
|
45
|
-
source.gsub!(/^[^:]
|
44
|
+
source.gsub!(/\A.*?(?=^:)/m, '') # beginning: strip every line in the beginning that does not start with a :
|
45
|
+
source.gsub!(/^[^:]*\z/, '') # end: strip every line in the end that does not start with a :
|
46
46
|
source.strip!
|
47
47
|
end
|
48
48
|
|
data/lib/cmxl/version.rb
CHANGED
data/lib/cmxl.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'cmxl/version'
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'rchardet'
|
4
4
|
|
5
5
|
require 'cmxl/field'
|
6
6
|
require 'cmxl/statement'
|
@@ -11,7 +11,11 @@ module Cmxl
|
|
11
11
|
@config
|
12
12
|
end
|
13
13
|
@config = {
|
14
|
-
|
14
|
+
# One or more newlines
|
15
|
+
# followed by `-` at the beginning of a line.
|
16
|
+
# "Eats up" but does not require characters until the end of the line and more newlines.
|
17
|
+
# \R is a platform independent newline but in the negated group `[^\n\r]` that did not seem to work.
|
18
|
+
statement_separator: /\R+-[^\n\r]*\R*/m,
|
15
19
|
raise_line_format_errors: true,
|
16
20
|
strip_headers: false
|
17
21
|
}
|
@@ -31,8 +35,8 @@ module Cmxl
|
|
31
35
|
def self.parse(data, options = {})
|
32
36
|
options[:statement_separator] ||= config[:statement_separator]
|
33
37
|
# if no encoding is provided we try to guess using CharDet
|
34
|
-
if options[:encoding].nil? && cd = CharDet.detect(data
|
35
|
-
options[:encoding] = cd
|
38
|
+
if options[:encoding].nil? && cd = CharDet.detect(data)
|
39
|
+
options[:encoding] = cd['encoding']
|
36
40
|
end
|
37
41
|
|
38
42
|
if options[:encoding]
|
@@ -7,7 +7,7 @@ describe Cmxl::Fields::Transaction do
|
|
7
7
|
subject(:ocmt_cghs_transaction) { Cmxl::Fields::Transaction.parse(fixture_line(:statement_ocmt_chgs)) }
|
8
8
|
subject(:supplementary_transaction) { Cmxl::Fields::Transaction.parse(fixture_line(:statement_supplementary_plain)) }
|
9
9
|
subject(:complex_supplementary_transaction) { Cmxl::Fields::Transaction.parse(fixture_line(:statement_supplementary_complex)) }
|
10
|
-
subject(:
|
10
|
+
subject(:valuta_after_entry_date) { Cmxl::Fields::Transaction.parse(fixture[3]) }
|
11
11
|
subject(:entry_before_valuta_transaction) { Cmxl::Fields::Transaction.parse(fixture[2]) }
|
12
12
|
subject(:transaction_type_swift) { Cmxl::Fields::Transaction.parse(fixture[4]) }
|
13
13
|
|
@@ -70,6 +70,11 @@ describe Cmxl::Fields::Transaction do
|
|
70
70
|
end
|
71
71
|
|
72
72
|
context 'statement with complex supplementary' do
|
73
|
+
it 'future reference' do
|
74
|
+
result = Cmxl::Fields::Transaction.parse(':61:2412121212ED162,57NDDTNONREF//950\n')
|
75
|
+
expect(result.amount).to eql(162.57)
|
76
|
+
end
|
77
|
+
|
73
78
|
it { expect(complex_supplementary_transaction.initial_amount_in_cents).to eql(nil) }
|
74
79
|
it { expect(complex_supplementary_transaction.initial_currency).to eql(nil) }
|
75
80
|
|
@@ -82,14 +87,14 @@ describe Cmxl::Fields::Transaction do
|
|
82
87
|
end
|
83
88
|
|
84
89
|
context 'valuta and entry-date assumptions' do
|
85
|
-
it 'entry_date before valuta is recognized
|
90
|
+
it 'entry_date before valuta is recognized correctly when including year-change' do
|
86
91
|
expect(entry_before_valuta_transaction.date).to eql(Date.new(2014, 1, 10))
|
87
92
|
expect(entry_before_valuta_transaction.entry_date).to eql(Date.new(2013, 12, 24))
|
88
93
|
end
|
89
94
|
|
90
95
|
it 'entry_date after valuta is recognized correctly when including year-change' do
|
91
|
-
expect(
|
92
|
-
expect(
|
96
|
+
expect(valuta_after_entry_date.date).to eql(Date.new(2014, 12, 24))
|
97
|
+
expect(valuta_after_entry_date.entry_date).to eql(Date.new(2015, 1, 2))
|
93
98
|
end
|
94
99
|
end
|
95
100
|
|
@@ -107,4 +112,77 @@ describe Cmxl::Fields::Transaction do
|
|
107
112
|
it { expect(transaction_type_swift).not_to be_storno }
|
108
113
|
it { expect(transaction_type_swift.sign).to eql(1) }
|
109
114
|
end
|
115
|
+
|
116
|
+
describe '#credit_debit_indicator' do
|
117
|
+
it 'returns the credit_debit_indicator as debit' do
|
118
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902DR000000000001,62NTRF0000549855700010//025498557/000001')
|
119
|
+
expect(result.credit_debit_indicator).to eql('D')
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'returns the credit_debit_indicator as credit' do
|
123
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902CR000000000001,62NTRF0000549855700010//025498557/000001')
|
124
|
+
expect(result.credit_debit_indicator).to eql('C')
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'returns the credit_debit_indicator as reversal credit' do
|
128
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902RC000000000001,62NTRF0000549855700010//025498557/000001')
|
129
|
+
expect(result.credit_debit_indicator).to eql('RC')
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'returns the credit_debit_indicator as reversal debit' do
|
133
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902RD000000000001,62NTRF0000549855700010//025498557/000001')
|
134
|
+
expect(result.credit_debit_indicator).to eql('RD')
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'returns the credit_debit_indicator as expected credit' do
|
138
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902EC000000000001,62NTRF0000549855700010//025498557/000001')
|
139
|
+
expect(result.credit_debit_indicator).to eql('EC')
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe '#expected?' do
|
144
|
+
it 'returns true if the transaction is expected' do
|
145
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902EC000000000001,62NTRF0000549855700010//025498557/000001')
|
146
|
+
expect(result).to be_expected
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'returns false if the transaction is not expected' do
|
150
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902RD000000000001,62NTRF0000549855700010//025498557/000001')
|
151
|
+
expect(result).not_to be_expected
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe '#expected_credit?' do
|
156
|
+
it 'returns true if the transaction is expected and credit' do
|
157
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902EC000000000001,62NTRF0000549855700010//025498557/000001')
|
158
|
+
expect(result).to be_expected_credit
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'returns false if the transaction is not expected and credit' do
|
162
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902RC000000000001,62NTRF0000549855700010//025498557/000001')
|
163
|
+
expect(result).not_to be_expected_credit
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'returns false if the transaction is expected and debit' do
|
167
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902ED000000000001,62NTRF0000549855700010//025498557/000001')
|
168
|
+
expect(result).not_to be_expected_credit
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe '#expected_debit?' do
|
173
|
+
it 'returns true if the transaction is expected and debit' do
|
174
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902ED000000000001,62NTRF0000549855700010//025498557/000001')
|
175
|
+
expect(result).to be_expected_debit
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'returns false if the transaction is not expected and debit' do
|
179
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902RD000000000001,62NTRF0000549855700010//025498557/000001')
|
180
|
+
expect(result).not_to be_expected_debit
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'returns false if the transaction is expected and credit' do
|
184
|
+
result = Cmxl::Fields::Transaction.parse(':61:1409010902EC000000000001,62NTRF0000549855700010//025498557/000001')
|
185
|
+
expect(result).not_to be_expected_debit
|
186
|
+
end
|
187
|
+
end
|
110
188
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
ABNANL2A
|
2
|
+
940
|
3
|
+
ABNANL2A
|
4
|
+
:20:ABN AMRO BANK NV
|
5
|
+
:25:517852257
|
6
|
+
:28:19321/1
|
7
|
+
:60F:C110522EUR3236,28
|
8
|
+
:61:1105240524D9,N192NONREF
|
9
|
+
:86:GIRO 428428 KPN - DIGITENNE BETALINGSKENM. 000000042188659
|
10
|
+
5314606715 BETREFT FACTUUR D.D. 20-05-2011
|
11
|
+
INCL. 1,44 BTW
|
12
|
+
:61:1105210523D11,59N426NONREF
|
13
|
+
:86:BEA NR:XXX1234 21.05.11/12.54 DIRCKIII FIL2500 KATWIJK,PAS999
|
14
|
+
:61:1105230523D11,63N426NONREF
|
15
|
+
:86:BEA NR:XXX1234 23.05.11/09.08 DIGROS FIL1015 KATWIJK Z,PAS999
|
16
|
+
:61:1105220523D11,8N426NONREF
|
17
|
+
:86:BEA NR:XXX1234 22.05.11/14.25 MC DONALDS A44 LEIDEN,PAS999
|
18
|
+
:61:1105210523D13,45N426NONREF
|
19
|
+
:86:BEA NR:XXX1234 21.05.11/12.09 PRINCE FIL. 55 KATWIJK Z,PAS999
|
20
|
+
:61:1105210523D15,49N426NONREF
|
21
|
+
:86:BEA NR:XXX1234 21.05.11/12.55 DIRX FIL6017 KATWIJK ZH ,PAS999
|
22
|
+
|
23
|
+
:61:1105210523D107,N426NONREF
|
24
|
+
:86:BEA NR:XXX1234 21.05.11/12.04 HANS ANDERS OPT./056 KAT,PAS999
|
25
|
+
:61:1105220523D141,48N426NONREF
|
26
|
+
:86:BEA NR:XXX1234 22.05.11/13.45 MYCOM DEN HAAG S-GRAVEN,PAS999
|
27
|
+
:62F:C110523EUR876,84
|
28
|
+
|
29
|
+
-
|
30
|
+
|
31
|
+
ABNANL2A
|
32
|
+
940
|
33
|
+
ABNANL2A
|
34
|
+
:20:ABN AMRO BANK NV
|
35
|
+
:25:517852257
|
36
|
+
:28:19322/1
|
37
|
+
:60F:C110523EUR2876,84
|
38
|
+
:61:1105240524D9,49N426NONREF
|
39
|
+
:86:BEA NR:XXX1234 24.05.11/09.18 PETS PLACE KATWIJK KATWI,PAS999
|
40
|
+
:61:1105240524D15,N426NONREF
|
41
|
+
:86:52.89.39.882 MYCOM DEN HAAG S-GRAVEN,PAS999
|
42
|
+
:62F:C110524EUR1849,75
|
43
|
+
|
44
|
+
-
|
@@ -0,0 +1,16 @@
|
|
1
|
+
ä4:
|
2
|
+
:20:5566778899100112
|
3
|
+
:25:10020030/1234567
|
4
|
+
:28C:188/1
|
5
|
+
:60F:C130928SEK0,
|
6
|
+
:62F:C130930SEK0,
|
7
|
+
:64:C130930SEK0,
|
8
|
+
-å
|
9
|
+
ä4:
|
10
|
+
:20:5566778899100169
|
11
|
+
:25:10020030/1234567
|
12
|
+
:28C:188/1
|
13
|
+
:60F:C130928SEK0,
|
14
|
+
:62F:C130930SEK0,
|
15
|
+
:64:C130930SEK0,
|
16
|
+
-å
|
@@ -0,0 +1,16 @@
|
|
1
|
+
{1:D02AASDISLNETAXXXXXXXXXXXXX}
|
2
|
+
{2:E623XXXXXXXXAXXXN}
|
3
|
+
{4:
|
4
|
+
:20:1234567
|
5
|
+
:21:9876543210
|
6
|
+
:25:10020030/1234567
|
7
|
+
:28C:5/1
|
8
|
+
:60F:C160314EUR2187,95
|
9
|
+
:61:0211011102DR800,NSTONONREF//55555
|
10
|
+
:86:008?00DAUERAUFTRAG?100599?20Miete November?3010020030?31234567?32MUELLER?34339
|
11
|
+
:61:0211021102CR3000,NTRFNONREF//55555
|
12
|
+
:86:051?00UEBERWEISUNG?100599?20Gehalt Oktober?21Firma
|
13
|
+
Mustermann GmbH?3050060400?310847564700?32MUELLER?34339
|
14
|
+
:62F:C160315EUR4387,95
|
15
|
+
:86:Some random data
|
16
|
+
-}
|
data/spec/mt940_parsing_spec.rb
CHANGED
@@ -95,4 +95,115 @@ describe 'parsing a statement' do
|
|
95
95
|
expect(subject[0].transactions.count).to eql(1)
|
96
96
|
end
|
97
97
|
end
|
98
|
+
|
99
|
+
describe "MT940 abnamro" do
|
100
|
+
it "splits the file into on statement with the headers included" do
|
101
|
+
expected_data = <<~MT940.chomp
|
102
|
+
ABNANL2A
|
103
|
+
940
|
104
|
+
ABNANL2A
|
105
|
+
:20:ABN AMRO BANK NV
|
106
|
+
:25:517852257
|
107
|
+
:28:19321/1
|
108
|
+
:60F:C110522EUR3236,28
|
109
|
+
:61:1105240524D9,N192NONREF
|
110
|
+
:86:GIRO 428428 KPN - DIGITENNE BETALINGSKENM. 000000042188659
|
111
|
+
5314606715 BETREFT FACTUUR D.D. 20-05-2011
|
112
|
+
INCL. 1,44 BTW
|
113
|
+
:61:1105210523D11,59N426NONREF
|
114
|
+
:86:BEA NR:XXX1234 21.05.11/12.54 DIRCKIII FIL2500 KATWIJK,PAS999
|
115
|
+
:61:1105230523D11,63N426NONREF
|
116
|
+
:86:BEA NR:XXX1234 23.05.11/09.08 DIGROS FIL1015 KATWIJK Z,PAS999
|
117
|
+
:61:1105220523D11,8N426NONREF
|
118
|
+
:86:BEA NR:XXX1234 22.05.11/14.25 MC DONALDS A44 LEIDEN,PAS999
|
119
|
+
:61:1105210523D13,45N426NONREF
|
120
|
+
:86:BEA NR:XXX1234 21.05.11/12.09 PRINCE FIL. 55 KATWIJK Z,PAS999
|
121
|
+
:61:1105210523D15,49N426NONREF
|
122
|
+
:86:BEA NR:XXX1234 21.05.11/12.55 DIRX FIL6017 KATWIJK ZH ,PAS999
|
123
|
+
|
124
|
+
:61:1105210523D107,N426NONREF
|
125
|
+
:86:BEA NR:XXX1234 21.05.11/12.04 HANS ANDERS OPT./056 KAT,PAS999
|
126
|
+
:61:1105220523D141,48N426NONREF
|
127
|
+
:86:BEA NR:XXX1234 22.05.11/13.45 MYCOM DEN HAAG S-GRAVEN,PAS999
|
128
|
+
:62F:C110523EUR876,84
|
129
|
+
MT940
|
130
|
+
stub = instance_double(Cmxl::Statement)
|
131
|
+
allow(Cmxl::Statement).to receive(:new).and_return(stub)
|
132
|
+
|
133
|
+
Cmxl.parse(mt940_file('mt940-abnamro'))
|
134
|
+
|
135
|
+
expect(Cmxl::Statement).to have_received(:new).with(expected_data)
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'splits the file into two statements' do
|
139
|
+
allow(Cmxl::Statement).to receive(:new)
|
140
|
+
|
141
|
+
Cmxl.parse(mt940_file('mt940-abnamro'))
|
142
|
+
|
143
|
+
expect(Cmxl::Statement).to have_received(:new).twice
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe 'MT940 handelsbank' do
|
148
|
+
it 'splits the file with the special characters correctly' do
|
149
|
+
expected_data = <<~MT940.chomp
|
150
|
+
채4:
|
151
|
+
:20:5566778899100112
|
152
|
+
:25:10020030/1234567
|
153
|
+
:28C:188/1
|
154
|
+
:60F:C130928SEK0,
|
155
|
+
:62F:C130930SEK0,
|
156
|
+
:64:C130930SEK0,
|
157
|
+
MT940
|
158
|
+
stub = instance_double(Cmxl::Statement)
|
159
|
+
allow(Cmxl::Statement).to receive(:new).and_return(stub)
|
160
|
+
|
161
|
+
Cmxl.parse(mt940_file('mt940-handelsbank'))
|
162
|
+
|
163
|
+
expect(Cmxl::Statement).to have_received(:new).with(expected_data)
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'splits the file into two statements' do
|
167
|
+
allow(Cmxl::Statement).to receive(:new)
|
168
|
+
|
169
|
+
Cmxl.parse(mt940_file('mt940-handelsbank'))
|
170
|
+
|
171
|
+
expect(Cmxl::Statement).to have_received(:new).twice
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "MT940 windows line breaks" do
|
176
|
+
it 'splits the file with the special characters correctly' do
|
177
|
+
expected_data =
|
178
|
+
"{1:D02AASDISLNETAXXXXXXXXXXXXX}\r\n"\
|
179
|
+
"{2:E623XXXXXXXXAXXXN}\r\n"\
|
180
|
+
"{4:\r\n"\
|
181
|
+
":20:1234567\r\n"\
|
182
|
+
":21:9876543210\r\n"\
|
183
|
+
":25:10020030/1234567\r\n"\
|
184
|
+
":28C:5/1\r\n"\
|
185
|
+
":60F:C160314EUR2187,95\r\n"\
|
186
|
+
":61:0211011102DR800,NSTONONREF//55555\r\n"\
|
187
|
+
":86:008?00DAUERAUFTRAG?100599?20Miete November?3010020030?31234567?32MUELLER?34339\r\n"\
|
188
|
+
":61:0211021102CR3000,NTRFNONREF//55555\r\n"\
|
189
|
+
":86:051?00UEBERWEISUNG?100599?20Gehalt Oktober?21Firma\r\n"\
|
190
|
+
"Mustermann GmbH?3050060400?310847564700?32MUELLER?34339\r\n"\
|
191
|
+
":62F:C160315EUR4387,95\r\n"\
|
192
|
+
":86:Some random data"
|
193
|
+
stub = instance_double(Cmxl::Statement)
|
194
|
+
allow(Cmxl::Statement).to receive(:new).and_return(stub)
|
195
|
+
|
196
|
+
Cmxl.parse(mt940_file('mt940-windows-line-breaks'))
|
197
|
+
|
198
|
+
expect(Cmxl::Statement).to have_received(:new).with(expected_data)
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'splits the file into two statements' do
|
202
|
+
allow(Cmxl::Statement).to receive(:new)
|
203
|
+
|
204
|
+
Cmxl.parse(mt940_file('mt940-windows-line-breaks'))
|
205
|
+
|
206
|
+
expect(Cmxl::Statement).to have_received(:new).once
|
207
|
+
end
|
208
|
+
end
|
98
209
|
end
|
data/spec/statement_spec.rb
CHANGED
@@ -27,9 +27,12 @@ describe Cmxl do
|
|
27
27
|
'bank_reference' => '025498557/000001',
|
28
28
|
'amount_in_cents' => 162,
|
29
29
|
'sign' => -1,
|
30
|
+
'credit_debit_indicator' => 'D',
|
30
31
|
'debit' => true,
|
31
32
|
'credit' => false,
|
32
33
|
'storno' => false,
|
34
|
+
'reversal' => false,
|
35
|
+
'expected' => false,
|
33
36
|
'bic' => 'HYVEDEMMXXX',
|
34
37
|
'iban' => 'HUkkbbbsssskcccccccccccccccx',
|
35
38
|
'name' => 'Peter Pan',
|
@@ -81,6 +84,7 @@ describe Cmxl do
|
|
81
84
|
'sha' => '3c5e65aa3d3878b06b58b6f1ae2f3693004dfb04e3ab7119a1c1244e612293da',
|
82
85
|
'entry_date' => Date.new(2014, 9, 2),
|
83
86
|
'funds_code' => 'D',
|
87
|
+
'credit_debit_indicator' => 'D',
|
84
88
|
'currency_letter' => 'R',
|
85
89
|
'amount' => 1.62,
|
86
90
|
'swift_code' => 'NTRF',
|
@@ -90,12 +94,14 @@ describe Cmxl do
|
|
90
94
|
'sign' => -1,
|
91
95
|
'debit' => true,
|
92
96
|
'credit' => false,
|
93
|
-
'storno' => false
|
97
|
+
'storno' => false,
|
98
|
+
'reversal' => false,
|
99
|
+
'expected' => false,
|
94
100
|
)
|
95
101
|
end
|
96
102
|
end
|
97
103
|
|
98
|
-
describe 'statement issued over a years
|
104
|
+
describe 'statement issued over a years boundary' do
|
99
105
|
subject { Cmxl.parse(mt940_file('statement-mt940')).first.transactions.last }
|
100
106
|
|
101
107
|
it { expect(subject.mt942?).to be_falsey }
|
@@ -161,4 +167,88 @@ describe Cmxl do
|
|
161
167
|
it { expect(statement.generation_date).to eql(Date.new(2019, 1, 9)) }
|
162
168
|
end
|
163
169
|
|
170
|
+
describe "header parsing" do
|
171
|
+
context "when strip_headers is enabled" do
|
172
|
+
around do |example|
|
173
|
+
existing_value = Cmxl.config[:strip_headers]
|
174
|
+
Cmxl.config[:strip_headers] = true
|
175
|
+
example.run
|
176
|
+
Cmxl.config[:strip_headers] = existing_value
|
177
|
+
end
|
178
|
+
|
179
|
+
it "removes any headers" do
|
180
|
+
data = <<~MT940.chomp
|
181
|
+
{1:D02AASDISLNETAXXXXXXXXXXXXX}
|
182
|
+
{2:E623XXXXXXXXAXXXN}
|
183
|
+
{4:
|
184
|
+
:20:MT940/78374
|
185
|
+
:25:xxxxxxxxxxxxxx
|
186
|
+
:28C:3/1
|
187
|
+
:60F:C160201INR0,00
|
188
|
+
:61:3622687806CR1368378,92NMSC37935531
|
189
|
+
:86:-TX TRN-REF NO.1156ADS5601187 EUR 13456/TSV
|
190
|
+
:62F:C141387INR11 27421,94
|
191
|
+
-}
|
192
|
+
MT940
|
193
|
+
|
194
|
+
result = Cmxl::Statement.new(data)
|
195
|
+
|
196
|
+
expect(result.fields.count).to eq(6)
|
197
|
+
end
|
198
|
+
|
199
|
+
it "does nothing if there are no headers" do
|
200
|
+
data = <<~MT940.chomp
|
201
|
+
:20:MT940/78374
|
202
|
+
:25:xxxxxxxxxxxxxx
|
203
|
+
:28C:3/1
|
204
|
+
:60F:C160201INR0,00
|
205
|
+
:61:3622687806CR1368378,92NMSC37935531
|
206
|
+
:86:-TX TRN-REF NO.1156ADS5601187 EUR 13456/TSV
|
207
|
+
:62F:C141387INR11 27421,94
|
208
|
+
MT940
|
209
|
+
|
210
|
+
result = Cmxl::Statement.new(data)
|
211
|
+
|
212
|
+
expect(result.fields.count).to eq(6)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context "when strip_headers is disabled" do
|
217
|
+
it "raise an parsing error exception if headers are present" do
|
218
|
+
data = <<~MT940.chomp
|
219
|
+
{1:D02AASDISLNETAXXXXXXXXXXXXX}
|
220
|
+
{2:E623XXXXXXXXAXXXN}
|
221
|
+
{4:
|
222
|
+
:20:MT940/78374
|
223
|
+
:25:xxxxxxxxxxxxxx
|
224
|
+
:28C:3/1
|
225
|
+
:60F:C160201INR0,00
|
226
|
+
:61:3622687806CR1368378,92NMSC37935531
|
227
|
+
:86:-TX TRN-REF NO.1156ADS5601187 EUR 13456/TSV
|
228
|
+
:62F:C141387INR11 27421,94
|
229
|
+
-}
|
230
|
+
MT940
|
231
|
+
|
232
|
+
expect{
|
233
|
+
Cmxl::Statement.new(data)
|
234
|
+
}.to raise_error(Cmxl::Field::LineFormatError)
|
235
|
+
end
|
236
|
+
|
237
|
+
it "extracts the field" do
|
238
|
+
data = <<~MT940.chomp
|
239
|
+
:20:MT940/78374
|
240
|
+
:25:xxxxxxxxxxxxxx
|
241
|
+
:28C:3/1
|
242
|
+
:60F:C160201INR0,00
|
243
|
+
:61:3622687806CR1368378,92NMSC37935531
|
244
|
+
:86:-TX TRN-REF NO.1156ADS5601187 EUR 13456/TSV
|
245
|
+
:62F:C141387INR11 27421,94
|
246
|
+
MT940
|
247
|
+
|
248
|
+
result = Cmxl::Statement.new(data)
|
249
|
+
|
250
|
+
expect(result.fields.count).to eq(6)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
164
254
|
end
|
metadata
CHANGED
@@ -1,15 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cmxl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1
|
4
|
+
version: '2.1'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Bumann
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-01-28 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: rchardet
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
13
26
|
- !ruby/object:Gem::Dependency
|
14
27
|
name: bundler
|
15
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,20 +93,6 @@ dependencies:
|
|
80
93
|
- - ">="
|
81
94
|
- !ruby/object:Gem::Version
|
82
95
|
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: rchardet19
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
type: :runtime
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
97
96
|
description: Cmxl provides an friendly, extensible and customizable parser for the
|
98
97
|
MT940 bank statement format.
|
99
98
|
email:
|
@@ -102,9 +101,10 @@ executables: []
|
|
102
101
|
extensions: []
|
103
102
|
extra_rdoc_files: []
|
104
103
|
files:
|
104
|
+
- ".github/workflows/ci.yml"
|
105
|
+
- ".github/workflows/release.yml"
|
105
106
|
- ".gitignore"
|
106
107
|
- ".rspec"
|
107
|
-
- ".travis.yml"
|
108
108
|
- CHANGELOG.mdown
|
109
109
|
- Gemfile
|
110
110
|
- LICENSE.txt
|
@@ -163,9 +163,12 @@ files:
|
|
163
163
|
- spec/fixtures/lines/statement_supplementary_plain.txt
|
164
164
|
- spec/fixtures/lines/vmk_summary_credit.txt
|
165
165
|
- spec/fixtures/lines/vmk_summary_debit.txt
|
166
|
+
- spec/fixtures/mt940-abnamro.txt
|
166
167
|
- spec/fixtures/mt940-deutsche_bank.txt
|
168
|
+
- spec/fixtures/mt940-handelsbank.txt
|
167
169
|
- spec/fixtures/mt940-headers.txt
|
168
170
|
- spec/fixtures/mt940-iso8859-1.txt
|
171
|
+
- spec/fixtures/mt940-windows-line-breaks.txt
|
169
172
|
- spec/fixtures/mt940-with-colon-after-line-break.txt
|
170
173
|
- spec/fixtures/mt940-with-detailed-end-balance.txt
|
171
174
|
- spec/fixtures/mt940.txt
|
@@ -198,8 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
198
201
|
- !ruby/object:Gem::Version
|
199
202
|
version: '0'
|
200
203
|
requirements: []
|
201
|
-
rubygems_version: 3.
|
202
|
-
signing_key:
|
204
|
+
rubygems_version: 3.6.2
|
203
205
|
specification_version: 4
|
204
206
|
summary: Cmxl is your friendly MT940 bank statement parser
|
205
207
|
test_files:
|
@@ -239,9 +241,12 @@ test_files:
|
|
239
241
|
- spec/fixtures/lines/statement_supplementary_plain.txt
|
240
242
|
- spec/fixtures/lines/vmk_summary_credit.txt
|
241
243
|
- spec/fixtures/lines/vmk_summary_debit.txt
|
244
|
+
- spec/fixtures/mt940-abnamro.txt
|
242
245
|
- spec/fixtures/mt940-deutsche_bank.txt
|
246
|
+
- spec/fixtures/mt940-handelsbank.txt
|
243
247
|
- spec/fixtures/mt940-headers.txt
|
244
248
|
- spec/fixtures/mt940-iso8859-1.txt
|
249
|
+
- spec/fixtures/mt940-windows-line-breaks.txt
|
245
250
|
- spec/fixtures/mt940-with-colon-after-line-break.txt
|
246
251
|
- spec/fixtures/mt940-with-detailed-end-balance.txt
|
247
252
|
- spec/fixtures/mt940.txt
|