mt940_parser 1.5.3 → 1.5.5
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/lint.yml +29 -0
- data/.github/workflows/tests.yml +22 -0
- data/.rubocop.yml +61 -0
- data/.tool-versions +1 -0
- data/README.md +2 -1
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/mt940/customer_statement_message.rb +34 -23
- data/lib/mt940/version.rb +1 -1
- data/lib/mt940.rb +48 -42
- data/lib/mt940_parser.rb +3 -0
- data/mt940_parser.gemspec +8 -6
- data/test/test_customer_statement_message.rb +27 -16
- data/test/test_mt940.rb +4 -1
- metadata +23 -4
- data/.travis.yml +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83c746f0f3871275400a75662a116760798fa3212f443588965c03cb53bfc7a5
|
4
|
+
data.tar.gz: f96ef50e6f342890620391ade43d1034a34df62e7320f95502b473abe9d71a3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9ba3439db4b3e5bb1595ac608c9060a453d0a0b3a3b56a4dd41e5f9eb68d017834932723ad574b0f07f0828935313dd966bec679de825060ca746fec2f84432
|
7
|
+
data.tar.gz: 2eacc5076f62817e7b1822426573ae8c2efddc6ecaf8af9d7642cb378490fd9d3d40c481ec7a3483c36077dc191eeac3aab5b05e6f28d0d6953256e379ab450d
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# based on https://github.com/rails/rails/blob/4a78dcb/.github/workflows/rubocop.yml
|
2
|
+
|
3
|
+
name: rubocop linting
|
4
|
+
|
5
|
+
on: [push, pull_request]
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
build:
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
|
11
|
+
steps:
|
12
|
+
- uses: actions/checkout@v2
|
13
|
+
- name: Set up Ruby
|
14
|
+
uses: ruby/setup-ruby@v1
|
15
|
+
with:
|
16
|
+
ruby-version: 2.7
|
17
|
+
- name: Cache gems
|
18
|
+
uses: actions/cache@v1
|
19
|
+
with:
|
20
|
+
path: vendor/bundle
|
21
|
+
key: ${{ runner.os }}-rubocop-${{ hashFiles('**/Gemfile.lock') }}
|
22
|
+
restore-keys: |
|
23
|
+
${{ runner.os }}-rubocop-
|
24
|
+
- name: Install gems
|
25
|
+
run: |
|
26
|
+
bundle config path vendor/bundle
|
27
|
+
bundle install --jobs 4 --retry 3
|
28
|
+
- name: Run rubocop
|
29
|
+
run: bundle exec rubocop --lint
|
@@ -0,0 +1,22 @@
|
|
1
|
+
name: tests
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
|
9
|
+
strategy:
|
10
|
+
matrix:
|
11
|
+
ruby: [ '2.7', 'ruby-head', 'jruby-head' ]
|
12
|
+
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v2
|
15
|
+
- name: Set up Ruby ${{ matrix.ruby }}
|
16
|
+
uses: ruby/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
ruby-version: ${{ matrix.ruby }}
|
19
|
+
- name: Install dependencies
|
20
|
+
run: bundle install --jobs 4
|
21
|
+
- name: Test with Rake
|
22
|
+
run: bundle exec rake
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# usage: `rubocop --lint`
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
NewCops: enable
|
5
|
+
|
6
|
+
# disable some linters that are not so likely to indicate bugs
|
7
|
+
|
8
|
+
Lint/AmbiguousAssignment:
|
9
|
+
Enabled: false
|
10
|
+
Lint/AmbiguousBlockAssociation:
|
11
|
+
Enabled: false
|
12
|
+
Lint/AmbiguousOperator:
|
13
|
+
Enabled: false
|
14
|
+
Lint/AmbiguousRegexpLiteral:
|
15
|
+
Enabled: false
|
16
|
+
Lint/AssignmentInCondition:
|
17
|
+
Enabled: false
|
18
|
+
Lint/ConstantDefinitionInBlock:
|
19
|
+
Enabled: false
|
20
|
+
Lint/ConstantResolution:
|
21
|
+
Enabled: false
|
22
|
+
Lint/DuplicateBranch:
|
23
|
+
Enabled: false
|
24
|
+
Lint/EmptyBlock:
|
25
|
+
Enabled: false
|
26
|
+
Lint/EmptyClass:
|
27
|
+
Enabled: false
|
28
|
+
Lint/EmptyConditionalBody:
|
29
|
+
Enabled: false
|
30
|
+
Lint/EmptyExpression:
|
31
|
+
Enabled: false
|
32
|
+
Lint/EmptyFile:
|
33
|
+
Enabled: false
|
34
|
+
Lint/EmptyWhen:
|
35
|
+
Enabled: false
|
36
|
+
Lint/EnsureReturn:
|
37
|
+
Enabled: false
|
38
|
+
Lint/Loop:
|
39
|
+
Enabled: false
|
40
|
+
Lint/MissingSuper:
|
41
|
+
Enabled: false
|
42
|
+
Lint/MixedRegexpCaptureTypes:
|
43
|
+
Enabled: false
|
44
|
+
Lint/NumberConversion:
|
45
|
+
Enabled: false
|
46
|
+
Lint/ParenthesesAsGroupedExpression:
|
47
|
+
Enabled: false
|
48
|
+
Lint/RedundantStringCoercion:
|
49
|
+
Enabled: false
|
50
|
+
Lint/ShadowedArgument:
|
51
|
+
Enabled: false
|
52
|
+
Lint/ShadowedException:
|
53
|
+
Enabled: false
|
54
|
+
Lint/ShadowingOuterLocalVariable:
|
55
|
+
Enabled: false
|
56
|
+
Lint/SuppressedException:
|
57
|
+
Enabled: false
|
58
|
+
Lint/UnusedBlockArgument:
|
59
|
+
Enabled: false
|
60
|
+
Lint/UnusedMethodArgument:
|
61
|
+
Enabled: false
|
data/.tool-versions
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby 3.1.0
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
[](http://badge.fury.io/rb/mt940_parser)
|
1
|
+
[](http://badge.fury.io/rb/mt940_parser)
|
2
|
+
[](https://github.com/betterplace/mt940_parser/actions)
|
2
3
|
|
3
4
|
# mt940_parser
|
4
5
|
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.5.
|
1
|
+
1.5.5
|
@@ -3,11 +3,10 @@
|
|
3
3
|
# the data easier
|
4
4
|
class MT940
|
5
5
|
class CustomerStatementMessage
|
6
|
-
|
7
|
-
attr_reader :account, :statement_lines
|
6
|
+
attr_reader :account, :statement_lines, :opening_balance, :closing_balance
|
8
7
|
|
9
8
|
def self.parse_file(file)
|
10
|
-
|
9
|
+
parse(File.read(file))
|
11
10
|
end
|
12
11
|
|
13
12
|
def self.parse(data)
|
@@ -16,12 +15,15 @@ class MT940
|
|
16
15
|
end
|
17
16
|
|
18
17
|
def initialize(lines)
|
19
|
-
@account = lines
|
18
|
+
@account = select_by_type(lines, MT940::AccountIdentification)
|
19
|
+
@opening_balance = select_by_type(lines, MT940::AccountBalance)
|
20
|
+
@closing_balance = select_by_type(lines, MT940::ClosingBalance)
|
20
21
|
@statement_lines = []
|
21
22
|
lines.each_with_index do |line, i|
|
22
23
|
next unless line.class == MT940::StatementLine
|
23
|
-
|
24
|
-
|
24
|
+
|
25
|
+
ensure_is_info_line!(lines[i + 1])
|
26
|
+
@statement_lines << StatementLineBundle.new(lines[i], lines[i + 1])
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
@@ -33,36 +35,45 @@ class MT940
|
|
33
35
|
@account.account_number
|
34
36
|
end
|
35
37
|
|
38
|
+
def signature
|
39
|
+
Digest::SHA256.hexdigest(opening_balance.content.to_s + closing_balance.content.to_s)
|
40
|
+
end
|
41
|
+
|
36
42
|
private
|
37
43
|
|
44
|
+
def select_by_type(lines, line_klass)
|
45
|
+
lines.select { |line| line.instance_of?(line_klass) }.first
|
46
|
+
end
|
47
|
+
|
38
48
|
def ensure_is_info_line!(line)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
49
|
+
return if line.is_a? MT940::StatementLineInformation
|
50
|
+
|
51
|
+
raise Errors::UnexpectedStructureError,
|
52
|
+
'Unexpected Structure; expected StatementLineInformation, ' \
|
53
|
+
"but was #{line.class}"
|
44
54
|
end
|
45
55
|
end
|
46
56
|
|
47
57
|
class StatementLineBundle
|
48
58
|
METHOD_MAP = {
|
49
|
-
:
|
50
|
-
:
|
51
|
-
:
|
52
|
-
:
|
53
|
-
:
|
54
|
-
:
|
55
|
-
:
|
56
|
-
:
|
57
|
-
:
|
58
|
-
:
|
59
|
+
amount: :line,
|
60
|
+
funds_code: :line,
|
61
|
+
value_date: :line,
|
62
|
+
entry_date: :line,
|
63
|
+
account_holder: :info,
|
64
|
+
details: :info,
|
65
|
+
account_number: :info,
|
66
|
+
bank_code: :info,
|
67
|
+
code: :info,
|
68
|
+
transaction_description: :info
|
59
69
|
}
|
60
70
|
|
61
71
|
def initialize(statement_line, statement_line_info)
|
62
|
-
@line
|
72
|
+
@line = statement_line
|
73
|
+
@info = statement_line_info
|
63
74
|
end
|
64
75
|
|
65
|
-
def method_missing(method, *args, &
|
76
|
+
def method_missing(method, *args, &)
|
66
77
|
super unless METHOD_MAP.has_key?(method)
|
67
78
|
object = instance_variable_get("@#{METHOD_MAP[method.to_sym]}")
|
68
79
|
object.send(method)
|
data/lib/mt940/version.rb
CHANGED
data/lib/mt940.rb
CHANGED
@@ -4,6 +4,7 @@ require 'mt940/customer_statement_message'
|
|
4
4
|
require 'bigdecimal'
|
5
5
|
require 'bigdecimal/util'
|
6
6
|
require 'date'
|
7
|
+
require 'digest/sha2'
|
7
8
|
|
8
9
|
class MT940
|
9
10
|
class Field
|
@@ -13,10 +14,13 @@ class MT940
|
|
13
14
|
SHORT_DATE = /(\d{2})(\d{2})/
|
14
15
|
|
15
16
|
class << self
|
17
|
+
LINE = /^:(\d{2})(\w)?:(.*)$/
|
16
18
|
|
17
19
|
def for(line)
|
18
|
-
if line.match(
|
19
|
-
number
|
20
|
+
if line.match(LINE)
|
21
|
+
number = ::Regexp.last_match(1)
|
22
|
+
modifier = ::Regexp.last_match(2)
|
23
|
+
content = ::Regexp.last_match(3)
|
20
24
|
klass = {
|
21
25
|
'20' => Job,
|
22
26
|
'21' => Reference,
|
@@ -34,7 +38,9 @@ class MT940
|
|
34
38
|
|
35
39
|
klass.new(modifier, content)
|
36
40
|
else
|
37
|
-
raise Errors::WrongLineFormatError,
|
41
|
+
raise Errors::WrongLineFormatError,
|
42
|
+
"Wrong line format does not match #{LINE.inspect}. Got: " \
|
43
|
+
"#{line.dump[0...80]}#{'[...]' if line.dump.size > 80}"
|
38
44
|
end
|
39
45
|
end
|
40
46
|
end
|
@@ -55,15 +61,14 @@ class MT940
|
|
55
61
|
|
56
62
|
def parse_date(date)
|
57
63
|
date.match(DATE)
|
58
|
-
::Date.new("20#{
|
64
|
+
::Date.new("20#{::Regexp.last_match(1)}".to_i, ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i)
|
59
65
|
end
|
60
66
|
|
61
67
|
def parse_entry_date(raw_entry_date, value_date)
|
62
68
|
raw_entry_date.match(SHORT_DATE)
|
63
|
-
entry_date = ::Date.new(value_date.year,
|
64
|
-
unless entry_date.year == value_date.year
|
65
|
-
|
66
|
-
end
|
69
|
+
entry_date = ::Date.new(value_date.year, ::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i)
|
70
|
+
raise 'Unhandled case: value date and entry date are in different years' unless entry_date.year == value_date.year
|
71
|
+
|
67
72
|
entry_date
|
68
73
|
end
|
69
74
|
end
|
@@ -84,20 +89,21 @@ class MT940
|
|
84
89
|
# 25
|
85
90
|
class AccountIdentification < Field
|
86
91
|
attr_reader :account_identifier
|
87
|
-
|
92
|
+
|
93
|
+
CONTENT = /(.{1,35})/ # any 35 chars (35x from the docs)
|
88
94
|
|
89
95
|
def parse_content(content)
|
90
96
|
content.match(CONTENT)
|
91
|
-
@account_identifier =
|
97
|
+
@account_identifier = ::Regexp.last_match(1)
|
92
98
|
end
|
93
99
|
|
94
100
|
# fail over to the old Account class
|
95
|
-
def method_missing(method, *args, &
|
101
|
+
def method_missing(method, *args, &)
|
96
102
|
@fail_over_implementation ||= Account.new(@modifier, @content)
|
97
103
|
value = @fail_over_implementation.send(method)
|
98
|
-
warn
|
104
|
+
warn '[DEPRECATION]:'
|
99
105
|
warn "You used '#{method}' on the Account/AccountIdentification class"
|
100
|
-
warn
|
106
|
+
warn 'This field is not part of the MT940 specification but implementation specific'
|
101
107
|
warn "Please use the 'account_identifier' and parse yourself."
|
102
108
|
|
103
109
|
value
|
@@ -109,11 +115,13 @@ class MT940
|
|
109
115
|
class Account < Field
|
110
116
|
attr_reader :bank_code, :account_number, :account_currency
|
111
117
|
|
112
|
-
CONTENT =
|
118
|
+
CONTENT = %r{^(.{8,11})/(\d{0,23})([A-Z]{3})?$}
|
113
119
|
|
114
120
|
def parse_content(content)
|
115
121
|
content.match(CONTENT)
|
116
|
-
@bank_code
|
122
|
+
@bank_code = ::Regexp.last_match(1)
|
123
|
+
@account_number = ::Regexp.last_match(2)
|
124
|
+
@account_currency = ::Regexp.last_match(3)
|
117
125
|
end
|
118
126
|
end
|
119
127
|
|
@@ -121,14 +129,15 @@ class MT940
|
|
121
129
|
class Statement < Field
|
122
130
|
attr_reader :number, :sheet
|
123
131
|
|
124
|
-
CONTENT =
|
132
|
+
CONTENT = %r{^(0|(\d{5,5})/(\d{2,5}))$}
|
125
133
|
|
126
134
|
def parse_content(content)
|
127
135
|
content.match(CONTENT)
|
128
|
-
if
|
136
|
+
if ::Regexp.last_match(1) == '0'
|
129
137
|
@number = @sheet = 0
|
130
138
|
else
|
131
|
-
@number
|
139
|
+
@number = ::Regexp.last_match(2).to_i
|
140
|
+
@sheet = ::Regexp.last_match(3).to_i
|
132
141
|
end
|
133
142
|
end
|
134
143
|
end
|
@@ -151,23 +160,23 @@ class MT940
|
|
151
160
|
end
|
152
161
|
|
153
162
|
@sign =
|
154
|
-
case
|
163
|
+
case ::Regexp.last_match(1)
|
155
164
|
when 'C'
|
156
165
|
:credit
|
157
166
|
when 'D'
|
158
167
|
:debit
|
159
168
|
end
|
160
169
|
|
161
|
-
raw_date =
|
162
|
-
@currency =
|
163
|
-
@amount = parse_amount_in_cents(
|
170
|
+
raw_date = ::Regexp.last_match(2)
|
171
|
+
@currency = ::Regexp.last_match(3)
|
172
|
+
@amount = parse_amount_in_cents(::Regexp.last_match(4))
|
164
173
|
|
165
174
|
@date =
|
166
175
|
case raw_date
|
167
176
|
when 'ALT', '0'
|
168
177
|
nil
|
169
178
|
when DATE
|
170
|
-
::Date.new("20#{
|
179
|
+
::Date.new("20#{::Regexp.last_match(1)}".to_i, ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i)
|
171
180
|
end
|
172
181
|
end
|
173
182
|
end
|
@@ -176,15 +185,15 @@ class MT940
|
|
176
185
|
class StatementLine < Field
|
177
186
|
attr_reader :date, :entry_date, :funds_code, :amount, :swift_code, :reference, :transaction_description
|
178
187
|
|
179
|
-
CONTENT =
|
188
|
+
CONTENT = %r{^(\d{6})(\d{4})?(C|D|RC|RD)\D?(\d{1,12},\d{0,2})((?:N|F).{3})(NONREF|.{0,16})(?:$|//)(.*)}
|
180
189
|
|
181
190
|
def parse_content(content)
|
182
191
|
content.match(CONTENT)
|
183
192
|
|
184
|
-
raw_date =
|
185
|
-
raw_entry_date =
|
193
|
+
raw_date = ::Regexp.last_match(1)
|
194
|
+
raw_entry_date = ::Regexp.last_match(2)
|
186
195
|
@funds_code =
|
187
|
-
case
|
196
|
+
case ::Regexp.last_match(3)
|
188
197
|
when 'C'
|
189
198
|
:credit
|
190
199
|
when 'D'
|
@@ -195,10 +204,10 @@ class MT940
|
|
195
204
|
:return_debit
|
196
205
|
end
|
197
206
|
|
198
|
-
@amount = parse_amount_in_cents(
|
199
|
-
@swift_code =
|
200
|
-
@reference =
|
201
|
-
@transaction_description =
|
207
|
+
@amount = parse_amount_in_cents(::Regexp.last_match(4))
|
208
|
+
@swift_code = ::Regexp.last_match(5)
|
209
|
+
@reference = ::Regexp.last_match(6)
|
210
|
+
@transaction_description = ::Regexp.last_match(7)
|
202
211
|
|
203
212
|
@date = parse_date(raw_date)
|
204
213
|
@entry_date = parse_entry_date(raw_entry_date, @date) if raw_entry_date
|
@@ -224,17 +233,17 @@ class MT940
|
|
224
233
|
# 86
|
225
234
|
class StatementLineInformation < Field
|
226
235
|
attr_reader :code, :transaction_description, :prima_nota, :details, :bank_code, :account_number,
|
227
|
-
|
236
|
+
:account_holder, :text_key_extension, :not_implemented_fields
|
228
237
|
|
229
238
|
def parse_content(content)
|
230
239
|
content.match(/^(\d{3})((.).*)$/)
|
231
|
-
@code =
|
240
|
+
@code = ::Regexp.last_match(1).to_i
|
232
241
|
|
233
242
|
details = []
|
234
243
|
account_holder = []
|
235
244
|
|
236
|
-
if seperator =
|
237
|
-
sub_fields =
|
245
|
+
if seperator = ::Regexp.last_match(3)
|
246
|
+
sub_fields = ::Regexp.last_match(2).scan(
|
238
247
|
/#{Regexp.escape(seperator)}(\d{2})([^#{Regexp.escape(seperator)}]*)/
|
239
248
|
)
|
240
249
|
|
@@ -256,10 +265,8 @@ class MT940
|
|
256
265
|
@text_key_extension = content
|
257
266
|
else
|
258
267
|
@not_implemented_fields ||= []
|
259
|
-
@not_implemented_fields << [
|
260
|
-
if $DEBUG
|
261
|
-
warn "code not implemented: code:#{code} content: #{content.inspect}"
|
262
|
-
end
|
268
|
+
@not_implemented_fields << [code, content]
|
269
|
+
warn "code not implemented: code:#{code} content: #{content.inspect}" if $DEBUG
|
263
270
|
end
|
264
271
|
end
|
265
272
|
end
|
@@ -276,15 +283,14 @@ class MT940
|
|
276
283
|
raw_sheets = new_text.split(/^-\s*\r\n/).map do |sheet|
|
277
284
|
sheet.gsub(/\r\n(?!:\d{2}\w?:)/, '')
|
278
285
|
end
|
279
|
-
|
286
|
+
raw_sheets.map { |raw_sheet| parse_sheet(raw_sheet) }
|
280
287
|
end
|
281
288
|
|
282
289
|
private
|
283
290
|
|
284
291
|
def parse_sheet(sheet)
|
285
292
|
lines = sheet.split("\r\n")
|
286
|
-
|
287
|
-
fields
|
293
|
+
lines.reject { |line| line.empty? }.map { |line| Field.for(line) }
|
288
294
|
end
|
289
295
|
end
|
290
296
|
end
|
data/lib/mt940_parser.rb
ADDED
data/mt940_parser.gemspec
CHANGED
@@ -1,22 +1,22 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: mt940_parser 1.5.
|
2
|
+
# stub: mt940_parser 1.5.5 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "mt940_parser".freeze
|
6
|
-
s.version = "1.5.
|
6
|
+
s.version = "1.5.5"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib".freeze]
|
10
10
|
s.authors = ["Thies C. Arntzen".freeze, "Phillip Oertel".freeze]
|
11
|
-
s.date = "
|
11
|
+
s.date = "2025-05-21"
|
12
12
|
s.description = "Ruby library that parses account statements in the SWIFT MT940 format.".freeze
|
13
13
|
s.email = "developers@betterplace.org".freeze
|
14
|
-
s.extra_rdoc_files = ["README.md".freeze, "lib/mt940.rb".freeze, "lib/mt940/customer_statement_message.rb".freeze, "lib/mt940/errors.rb".freeze, "lib/mt940/version.rb".freeze]
|
15
|
-
s.files = [".document".freeze, ".
|
14
|
+
s.extra_rdoc_files = ["README.md".freeze, "lib/mt940.rb".freeze, "lib/mt940/customer_statement_message.rb".freeze, "lib/mt940/errors.rb".freeze, "lib/mt940/version.rb".freeze, "lib/mt940_parser.rb".freeze]
|
15
|
+
s.files = [".document".freeze, ".github/workflows/lint.yml".freeze, ".github/workflows/tests.yml".freeze, ".gitignore".freeze, ".rubocop.yml".freeze, ".specification".freeze, ".tool-versions".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "docs/0E0Y00DNY.pdf".freeze, "docs/FinTS_4.0_Formals.pdf".freeze, "docs/FinTS_4.0_Messages_Finanzdatenformate.pdf".freeze, "docs/MT940_Deutschland_Structure2002.pdf".freeze, "docs/SEPA_20MT940__Schnittstellenbeschreibung.pdf".freeze, "docs/mt940.pdf".freeze, "docs/swift_mt940_942.pdf".freeze, "docs/uebersicht_der_geschaeftsvorfallcodes_und_buchungs_textschluessel.pdf".freeze, "lib/mt940.rb".freeze, "lib/mt940/customer_statement_message.rb".freeze, "lib/mt940/errors.rb".freeze, "lib/mt940/version.rb".freeze, "lib/mt940_parser.rb".freeze, "mt940_parser.gemspec".freeze, "test/fixtures/amount_formats.txt".freeze, "test/fixtures/amount_formats.yml".freeze, "test/fixtures/colon_in_descriptor.txt".freeze, "test/fixtures/colon_in_descriptor.yml".freeze, "test/fixtures/currency_in_25.txt".freeze, "test/fixtures/currency_in_25.yml".freeze, "test/fixtures/empty_86.txt".freeze, "test/fixtures/empty_86.yml".freeze, "test/fixtures/empty_entry_date.txt".freeze, "test/fixtures/empty_entry_date.yml".freeze, "test/fixtures/empty_line.txt".freeze, "test/fixtures/empty_line.yml".freeze, "test/fixtures/missing_crlf_at_end.txt".freeze, "test/fixtures/missing_crlf_at_end.yml".freeze, "test/fixtures/sepa_mt9401.txt".freeze, "test/fixtures/sepa_mt9401.yml".freeze, "test/fixtures/sepa_snippet.txt".freeze, "test/fixtures/sepa_snippet_broken.txt".freeze, "test/fixtures/with_binary_character.txt".freeze, "test/fixtures/with_binary_character.yml".freeze, "test/test_account_identifier.rb".freeze, "test/test_customer_statement_message.rb".freeze, "test/test_helper.rb".freeze, "test/test_mt940.rb".freeze]
|
16
16
|
s.homepage = "http://github.com/betterplace/mt940_parser".freeze
|
17
17
|
s.licenses = ["MIT".freeze]
|
18
18
|
s.rdoc_options = ["--title".freeze, "Mt940Parser - MT940 parses account statements in the SWIFT MT940 format.".freeze, "--main".freeze, "README.md".freeze]
|
19
|
-
s.rubygems_version = "3.
|
19
|
+
s.rubygems_version = "3.3.3".freeze
|
20
20
|
s.summary = "MT940 parses account statements in the SWIFT MT940 format.".freeze
|
21
21
|
s.test_files = ["test/test_account_identifier.rb".freeze, "test/test_customer_statement_message.rb".freeze, "test/test_helper.rb".freeze, "test/test_mt940.rb".freeze]
|
22
22
|
|
@@ -27,8 +27,10 @@ Gem::Specification.new do |s|
|
|
27
27
|
if s.respond_to? :add_runtime_dependency then
|
28
28
|
s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 1.11.0"])
|
29
29
|
s.add_development_dependency(%q<test-unit>.freeze, [">= 0"])
|
30
|
+
s.add_development_dependency(%q<rubocop>.freeze, [">= 0"])
|
30
31
|
else
|
31
32
|
s.add_dependency(%q<gem_hadar>.freeze, ["~> 1.11.0"])
|
32
33
|
s.add_dependency(%q<test-unit>.freeze, [">= 0"])
|
34
|
+
s.add_dependency(%q<rubocop>.freeze, [">= 0"])
|
33
35
|
end
|
34
36
|
end
|
@@ -1,22 +1,20 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
|
4
3
|
# $DEBUG = true
|
5
4
|
class TestCustomerStatementMessage < Test::Unit::TestCase
|
6
|
-
|
7
5
|
def setup
|
8
|
-
file = File.dirname(__FILE__) +
|
6
|
+
file = File.dirname(__FILE__) + '/fixtures/sepa_snippet.txt'
|
9
7
|
messages = MT940::CustomerStatementMessage.parse_file(file)
|
10
8
|
@message = messages.first
|
11
9
|
@message_2 = messages.last
|
12
10
|
end
|
13
11
|
|
14
12
|
def test_it_should_know_the_bank_code
|
15
|
-
assert_equal
|
13
|
+
assert_equal '50880050', @message.bank_code
|
16
14
|
end
|
17
15
|
|
18
16
|
def test_it_should_know_the_account_number
|
19
|
-
assert_equal
|
17
|
+
assert_equal '0194787400888', @message.account_number
|
20
18
|
end
|
21
19
|
|
22
20
|
def test_it_should_have_an_account_identification
|
@@ -30,7 +28,7 @@ class TestCustomerStatementMessage < Test::Unit::TestCase
|
|
30
28
|
|
31
29
|
def test_statement_lines_should_have_amount_info_credit
|
32
30
|
line = @message.statement_lines.first
|
33
|
-
assert_equal
|
31
|
+
assert_equal 5_099_005, line.amount
|
34
32
|
assert_equal :credit, line.funds_code
|
35
33
|
end
|
36
34
|
|
@@ -47,12 +45,12 @@ class TestCustomerStatementMessage < Test::Unit::TestCase
|
|
47
45
|
|
48
46
|
def test_statement_lines_info_should_have_bank_code
|
49
47
|
line = @message.statement_lines.first
|
50
|
-
assert_equal
|
48
|
+
assert_equal 'DRESDEFF508', line.bank_code
|
51
49
|
end
|
52
50
|
|
53
51
|
def test_statement_lines_info_should_have_account_number
|
54
52
|
line = @message.statement_lines.first
|
55
|
-
assert_equal
|
53
|
+
assert_equal 'DE14508800500194785000', line.account_number
|
56
54
|
end
|
57
55
|
|
58
56
|
def test_statement_lines_should_have_details
|
@@ -62,32 +60,45 @@ class TestCustomerStatementMessage < Test::Unit::TestCase
|
|
62
60
|
|
63
61
|
def test_statement_lines_should_have_an_entry_date
|
64
62
|
line = @message.statement_lines.first
|
65
|
-
assert_equal Date.parse(
|
63
|
+
assert_equal Date.parse('2007-09-04'), line.entry_date
|
66
64
|
end
|
67
65
|
|
68
66
|
def test_statement_lines_should_have_a_value_date
|
69
67
|
line = @message.statement_lines.first
|
70
|
-
assert_equal Date.parse(
|
68
|
+
assert_equal Date.parse('2007-09-07'), line.value_date
|
71
69
|
end
|
72
70
|
|
73
71
|
def test_parsing_the_file_should_return_two_message_objects
|
74
|
-
file = File.dirname(__FILE__) +
|
72
|
+
file = File.dirname(__FILE__) + '/fixtures/sepa_snippet.txt'
|
75
73
|
messages = MT940::CustomerStatementMessage.parse_file(file)
|
76
74
|
assert_equal 2, messages.size
|
77
|
-
assert_equal
|
78
|
-
assert_equal
|
75
|
+
assert_equal '0194787400888', messages[0].account_number
|
76
|
+
assert_equal '0194791600888', messages[1].account_number
|
79
77
|
end
|
80
78
|
|
81
79
|
def test_parsing_a_file_with_broken_structure_should_raise_an_exception
|
82
|
-
file = File.dirname(__FILE__) +
|
80
|
+
file = File.dirname(__FILE__) + '/fixtures/sepa_snippet_broken.txt'
|
83
81
|
assert_raise(MT940::Errors::UnexpectedStructureError) do
|
84
82
|
MT940::CustomerStatementMessage.parse_file(file)
|
85
83
|
end
|
86
84
|
end
|
87
85
|
|
88
86
|
def test_should_raise_method_missing_when_asking_statement_lines_for_unknown_stuff
|
89
|
-
file = File.dirname(__FILE__) +
|
87
|
+
file = File.dirname(__FILE__) + '/fixtures/sepa_snippet.txt'
|
88
|
+
message = MT940::CustomerStatementMessage.parse_file(file).first
|
89
|
+
assert_raise(NoMethodError) { message.statement_lines.first.foo }
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_should_parse_opening_and_closing_saldo
|
93
|
+
file = File.dirname(__FILE__) + '/fixtures/sepa_snippet.txt'
|
94
|
+
message = MT940::CustomerStatementMessage.parse_file(file).first
|
95
|
+
assert_equal(message.opening_balance.amount, 76_665_649)
|
96
|
+
assert_equal(message.closing_balance.amount, 112_525_040)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_should_have_signature
|
100
|
+
file = File.dirname(__FILE__) + '/fixtures/sepa_snippet.txt'
|
90
101
|
message = MT940::CustomerStatementMessage.parse_file(file).first
|
91
|
-
|
102
|
+
assert_equal(message.signature, 'b16439633c7c52fed8e28f717f364e7ec1936f123aa56ab66b15662093cb60ca')
|
92
103
|
end
|
93
104
|
end
|
data/test/test_mt940.rb
CHANGED
@@ -9,8 +9,11 @@ class TestMt940 < Test::Unit::TestCase
|
|
9
9
|
data = MT940.parse(IO.read(file))
|
10
10
|
|
11
11
|
generated_structure_file = file.gsub(/.txt$/, ".yml")
|
12
|
+
# ruby 3.1 / psych 4 disallows loading classes from YAML by default
|
13
|
+
load_method = YAML.respond_to?(:unsafe_load_file) ? :unsafe_load_file : :load_file
|
12
14
|
|
13
|
-
assert_equal YAML
|
15
|
+
assert_equal YAML.send(load_method, generated_structure_file).to_yaml,
|
16
|
+
data.to_yaml
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mt940_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thies C. Arntzen
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2025-05-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: gem_hadar
|
@@ -39,6 +39,20 @@ dependencies:
|
|
39
39
|
- - ">="
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rubocop
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
42
56
|
description: Ruby library that parses account statements in the SWIFT MT940 format.
|
43
57
|
email: developers@betterplace.org
|
44
58
|
executables: []
|
@@ -49,11 +63,15 @@ extra_rdoc_files:
|
|
49
63
|
- lib/mt940/customer_statement_message.rb
|
50
64
|
- lib/mt940/errors.rb
|
51
65
|
- lib/mt940/version.rb
|
66
|
+
- lib/mt940_parser.rb
|
52
67
|
files:
|
53
68
|
- ".document"
|
69
|
+
- ".github/workflows/lint.yml"
|
70
|
+
- ".github/workflows/tests.yml"
|
54
71
|
- ".gitignore"
|
72
|
+
- ".rubocop.yml"
|
55
73
|
- ".specification"
|
56
|
-
- ".
|
74
|
+
- ".tool-versions"
|
57
75
|
- Gemfile
|
58
76
|
- LICENSE
|
59
77
|
- README.md
|
@@ -71,6 +89,7 @@ files:
|
|
71
89
|
- lib/mt940/customer_statement_message.rb
|
72
90
|
- lib/mt940/errors.rb
|
73
91
|
- lib/mt940/version.rb
|
92
|
+
- lib/mt940_parser.rb
|
74
93
|
- mt940_parser.gemspec
|
75
94
|
- test/fixtures/amount_formats.txt
|
76
95
|
- test/fixtures/amount_formats.yml
|
@@ -119,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
138
|
- !ruby/object:Gem::Version
|
120
139
|
version: '0'
|
121
140
|
requirements: []
|
122
|
-
rubygems_version: 3.
|
141
|
+
rubygems_version: 3.3.3
|
123
142
|
signing_key:
|
124
143
|
specification_version: 4
|
125
144
|
summary: MT940 parses account statements in the SWIFT MT940 format.
|
data/.travis.yml
DELETED