iso-iban 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/LICENSE.txt +8 -0
- data/README.markdown +45 -0
- data/Rakefile +19 -0
- data/data/iso-iban/specs.yaml +838 -0
- data/iso-iban.gemspec +42 -0
- data/lib/iso/iban.rb +324 -0
- data/lib/iso/iban/autoload.rb +5 -0
- data/lib/iso/iban/specification.rb +128 -0
- data/lib/iso/iban/version.rb +18 -0
- data/test/data/test_spec.yaml +23 -0
- data/test/lib/helper.rb +86 -0
- data/test/runner.rb +20 -0
- data/test/tmp/test_spec.yaml +23 -0
- data/test/unit/lib/iso/iban.rb +117 -0
- metadata +61 -0
data/iso-iban.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "iso-iban"
|
5
|
+
s.version = "0.0.1"
|
6
|
+
s.authors = "Stefan Rusterholz"
|
7
|
+
s.email = "stefan.rusterholz@gmail.com"
|
8
|
+
s.homepage = "https://github.com/apeiros/iso-iban"
|
9
|
+
s.license = 'BSD 2-Clause'
|
10
|
+
|
11
|
+
s.description = <<-DESCRIPTION.gsub(/^ /, '').chomp
|
12
|
+
ISO::IBAN implements IBAN (International Bank Account Number) specification as per ISO 13616-1.
|
13
|
+
It provides methods to generate valid IBAN numbers from components, or to validate a given IBAN.
|
14
|
+
DESCRIPTION
|
15
|
+
s.summary = <<-SUMMARY.gsub(/^ /, '').chomp
|
16
|
+
Utilities for International Bank Account Numbers (IBAN) as per ISO 13616-1.
|
17
|
+
SUMMARY
|
18
|
+
|
19
|
+
s.files =
|
20
|
+
Dir['bin/**/*'] +
|
21
|
+
Dir['data/**/*'] +
|
22
|
+
Dir['documentation/**/*'] +
|
23
|
+
Dir['lib/**/*'] +
|
24
|
+
Dir['rake/**/*'] +
|
25
|
+
Dir['test/**/*'] +
|
26
|
+
Dir['*.gemspec'] +
|
27
|
+
%w[
|
28
|
+
.yardopts
|
29
|
+
LICENSE.txt
|
30
|
+
Rakefile
|
31
|
+
README.markdown
|
32
|
+
]
|
33
|
+
|
34
|
+
if File.directory?('bin') then
|
35
|
+
s.executables = Dir.chdir('bin') { Dir.glob('**/*').select { |f| File.executable?(f) } }
|
36
|
+
end
|
37
|
+
|
38
|
+
s.required_ruby_version = ">= 1.9.2"
|
39
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1")
|
40
|
+
s.rubygems_version = "1.3.1"
|
41
|
+
s.specification_version = 3
|
42
|
+
end
|
data/lib/iso/iban.rb
ADDED
@@ -0,0 +1,324 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'iso/iban/specification'
|
4
|
+
require 'iso/iban/version'
|
5
|
+
|
6
|
+
module ISO
|
7
|
+
|
8
|
+
# IBAN - ISO 13616-1
|
9
|
+
#
|
10
|
+
# General IBAN Information
|
11
|
+
# ========================
|
12
|
+
#
|
13
|
+
# * What is an IBAN?
|
14
|
+
# IBAN stands for International Bank Account Number. It is the ISO 13616
|
15
|
+
# international standard for numbering bank accounts. In 2006, the
|
16
|
+
# International Organization for Standardization (ISO) designated SWIFT as
|
17
|
+
# the Registration Authority for ISO 13616.
|
18
|
+
#
|
19
|
+
# * Use
|
20
|
+
# The IBAN facilitates the communication and processing of cross-border
|
21
|
+
# transactions. It allows exchanging account identification details in a
|
22
|
+
# machine-readable form.
|
23
|
+
#
|
24
|
+
#
|
25
|
+
# The ISO 13616 IBAN Standard
|
26
|
+
# ===========================
|
27
|
+
#
|
28
|
+
# * Structure
|
29
|
+
# The IBAN structure is defined in ISO 13616-1 and consists of a two-letter
|
30
|
+
# ISO 3166-1 country code, followed by two check digits and up to thirty
|
31
|
+
# alphanumeric characters for a BBAN (Basic Bank Account Number) which has a
|
32
|
+
# fixed length per country and, included within it, a bank identifier with a
|
33
|
+
# fixed position and a fixed length per country. The check digits are
|
34
|
+
# calculated based on the scheme defined in ISO/IEC 7064 (MOD97-10).
|
35
|
+
#
|
36
|
+
# * Terms and definitions
|
37
|
+
# Bank identifier: The identifier that uniquely identifies the financial
|
38
|
+
# institution and, when appropriate, the branch of that financial institution
|
39
|
+
# servicing an account
|
40
|
+
#
|
41
|
+
# `In this registry, the branch identifier format is shown specifically, when
|
42
|
+
# present.`
|
43
|
+
#
|
44
|
+
# *BBAN*: basic bank account number: The identifier that uniquely identifies
|
45
|
+
# an individual account, at a specific financial institution, in a particular
|
46
|
+
# country. The BBAN includes a bank identifier of the financial institution
|
47
|
+
# servicing that account.
|
48
|
+
# *IBAN*: international bank account number: The expanded version of the
|
49
|
+
# basic bank account number (BBAN), intended for use internationally. The
|
50
|
+
# IBAN uniquely identifies an individual account, at a specific financial
|
51
|
+
# institution, in a particular country.
|
52
|
+
#
|
53
|
+
# * Submitters
|
54
|
+
# Nationally-agreed, ISO13616-compliant IBAN formats are submitted to the
|
55
|
+
# registration authority exclusively by the National Standards Body or the
|
56
|
+
# National Central Bank of the country.
|
57
|
+
class IBAN
|
58
|
+
include Comparable
|
59
|
+
|
60
|
+
# Character code translation used to convert an IBAN into its numeric
|
61
|
+
# (digits-only) form
|
62
|
+
CharacterCodes = Hash[('0'..'9').zip('0'..'9')+('a'..'z').zip(10..35)+('A'..'Z').zip(10..35)]
|
63
|
+
|
64
|
+
# All specifications, see ISO::IBAN::Specification
|
65
|
+
@specifications = nil
|
66
|
+
|
67
|
+
# Load the IBAN specifications file, which determines how the IBAN
|
68
|
+
# for any given country looks like.
|
69
|
+
#
|
70
|
+
# It will use the following sources in this order (first one which exists wins)
|
71
|
+
#
|
72
|
+
# * Path passed as spec_file parameter
|
73
|
+
# * Path provided by the env variable IBAN_SPECIFICATIONS
|
74
|
+
# * The file ../data/iso-iban/specs.yaml relative to the lib dir
|
75
|
+
# * The Gem datadir path
|
76
|
+
#
|
77
|
+
# @param [String] spec_file
|
78
|
+
# Override the default specifications file path.
|
79
|
+
#
|
80
|
+
# @return [self]
|
81
|
+
def self.load_specifications(spec_file=nil)
|
82
|
+
if spec_file then
|
83
|
+
# do nothing
|
84
|
+
elsif ENV['IBAN_SPECIFICATIONS'] then
|
85
|
+
spec_file = ENV['IBAN_SPECIFICATIONS']
|
86
|
+
else
|
87
|
+
spec_file = File.expand_path('../../../../data/iso-iban/specs.yaml', __FILE__)
|
88
|
+
spec_file = Gem.datadir('iso-iban')+'/specs.yaml' if defined?(Gem) && !File.file?(spec_file)
|
89
|
+
end
|
90
|
+
|
91
|
+
@specifications = ISO::IBAN::Specification.load_yaml(spec_file)
|
92
|
+
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [Hash<String => ISO::IBAN::Specification>]
|
97
|
+
# A hash with the country (ISO3166 2-letter) as key and the specification for that country as value
|
98
|
+
def self.specifications
|
99
|
+
@specifications || raise("No specifications have been loaded yet.")
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param [String] a2_country_code
|
103
|
+
# The country (ISO3166 2-letter), e.g. 'CH' or 'DE'.
|
104
|
+
#
|
105
|
+
# @return [ISO::IBAN::Specification]
|
106
|
+
# The specification for the given country
|
107
|
+
def self.specification(a2_country_code, *default, &default_block)
|
108
|
+
specifications.fetch(a2_country_code, *default, &default_block)
|
109
|
+
end
|
110
|
+
|
111
|
+
# @param [String] iban
|
112
|
+
# An IBAN number, either in compact or human format.
|
113
|
+
#
|
114
|
+
# @return [true, false]
|
115
|
+
# Whether the IBAN is valid.
|
116
|
+
# See {#validate} for details.
|
117
|
+
def self.valid?(iban)
|
118
|
+
new(iban).valid?
|
119
|
+
end
|
120
|
+
|
121
|
+
# @param [String] iban
|
122
|
+
# An IBAN number, either in compact or human format.
|
123
|
+
#
|
124
|
+
# @return [Array<Symbol>]
|
125
|
+
# An array with a code of all validation errors, empty if valid.
|
126
|
+
# See {#validate} for details.
|
127
|
+
def self.validate(iban)
|
128
|
+
new(iban).validate
|
129
|
+
end
|
130
|
+
|
131
|
+
# @param [String] iban
|
132
|
+
# The IBAN in either compact or human readable form.
|
133
|
+
#
|
134
|
+
# @return [String]
|
135
|
+
# The IBAN in compact form.
|
136
|
+
def self.strip(iban)
|
137
|
+
iban.tr(' -', '')
|
138
|
+
end
|
139
|
+
|
140
|
+
# Generate an IBAN from country code and components, automatically filling in the checksum.
|
141
|
+
#
|
142
|
+
# @example Generate an IBAN for UBS Switzerland with account number '12345'
|
143
|
+
# ISO::IBAN.generate('CH', '216', '12345') # => #<ISO::IBAN CH92 0021 6000 0000 1234 5>
|
144
|
+
#
|
145
|
+
# @param [String] country
|
146
|
+
# The ISO-3166 2-letter country code.
|
147
|
+
#
|
148
|
+
def self.generate(country, *components)
|
149
|
+
spec = specification(country)
|
150
|
+
justified = spec.component_lengths.zip(components).map { |length, component| component.rjust(length, "0") }
|
151
|
+
iban = new(country+'??'+justified.join(''))
|
152
|
+
iban.update_checksum!
|
153
|
+
|
154
|
+
iban
|
155
|
+
end
|
156
|
+
|
157
|
+
# Converts a String into its digits-only form, i.e. all characters a-z are replaced with their corresponding
|
158
|
+
# digit sequences, according to the IBAN specification.
|
159
|
+
#
|
160
|
+
# @param [String] string
|
161
|
+
# The string to convert into its numeric form.
|
162
|
+
#
|
163
|
+
# @return [String] The string in its numeric, digits-only form.
|
164
|
+
def self.numerify(string)
|
165
|
+
string.downcase.gsub(/\D/) { |char|
|
166
|
+
CharacterCodes.fetch(char) {
|
167
|
+
raise ArgumentError, "The string contains an invalid character #{char.inspect}"
|
168
|
+
}
|
169
|
+
}.to_i
|
170
|
+
end
|
171
|
+
|
172
|
+
# @return [String] The standard form of the IBAN for machine communication, without spaces.
|
173
|
+
attr_reader :compact
|
174
|
+
|
175
|
+
# @return [String] The ISO-3166 2-letter country code.
|
176
|
+
attr_reader :country
|
177
|
+
|
178
|
+
# @return [ISO::IBAN::Specification] The specification for this IBAN (determined by its country).
|
179
|
+
attr_reader :specification
|
180
|
+
|
181
|
+
# @param [String] iban
|
182
|
+
# The IBAN number, either in formatted, human readable or in compact form.
|
183
|
+
def initialize(iban)
|
184
|
+
raise ArgumentError, "String expected for iban, but got #{iban.class}" unless iban.is_a?(String)
|
185
|
+
|
186
|
+
@compact = self.class.strip(iban)
|
187
|
+
@country = iban[0,2]
|
188
|
+
@specification = self.class.specification(@country, nil)
|
189
|
+
end
|
190
|
+
|
191
|
+
# @example Formatted IBAN
|
192
|
+
#
|
193
|
+
# ISO::IBAN.new('CH')
|
194
|
+
#
|
195
|
+
# @return [String] The IBAN in its formatted form, which is more human readable than the compact form.
|
196
|
+
def formatted
|
197
|
+
@_formatted ||= @compact.gsub(/.{4}(?=.)/, '\0 ')
|
198
|
+
end
|
199
|
+
|
200
|
+
# @return [String]
|
201
|
+
# IBAN in its numeric form, i.e. all characters a-z are replaced with their corresponding
|
202
|
+
# digit sequences.
|
203
|
+
def numeric
|
204
|
+
self.class.numerify(@compact[4..-1]+@compact[0,4])
|
205
|
+
end
|
206
|
+
|
207
|
+
# @return [true, false]
|
208
|
+
# Whether the IBAN is valid.
|
209
|
+
# See {#validate} for details.
|
210
|
+
def valid?
|
211
|
+
validate.empty?
|
212
|
+
end
|
213
|
+
|
214
|
+
# Validation error codes:
|
215
|
+
#
|
216
|
+
# * :invalid_country
|
217
|
+
# * :invalid_checksum
|
218
|
+
# * :invalid_length
|
219
|
+
# * :invalid_format
|
220
|
+
#
|
221
|
+
# Invalid country means the country is unknown (char 1 & 2 in the IBAN).
|
222
|
+
# Invalid checksum means the two check digits (char 3 & 4 in the IBAN).
|
223
|
+
# Invalid length means the IBAN does not comply with the length specified for the country of that IBAN.
|
224
|
+
# Invalid format means the IBAN does not comply with the format specified for the country of that IBAN.
|
225
|
+
#
|
226
|
+
# @return [Array<Symbol>] An array with a code of all validation errors, empty if valid.
|
227
|
+
def validate
|
228
|
+
errors = []
|
229
|
+
errors << :invalid_country unless valid_country?
|
230
|
+
errors << :invalid_checksum unless valid_checksum?
|
231
|
+
errors << :invalid_length unless valid_length?
|
232
|
+
errors << :invalid_format unless valid_format?
|
233
|
+
|
234
|
+
errors
|
235
|
+
end
|
236
|
+
|
237
|
+
# @return [String] The checksum digits in the IBAN.
|
238
|
+
def checksum_digits
|
239
|
+
@compact[2,2]
|
240
|
+
end
|
241
|
+
|
242
|
+
# @return [String] The BBAN of the IBAN.
|
243
|
+
def bban
|
244
|
+
@compact[4..-1]
|
245
|
+
end
|
246
|
+
|
247
|
+
# @return [String, nil] The bank code part of the IBAN, nil if not applicable.
|
248
|
+
def bank_code
|
249
|
+
if @specification && @specification.bank_position_from && @specification.bank_position_to
|
250
|
+
@compact[@specification.bank_position_from..@specification.bank_position_to]
|
251
|
+
else
|
252
|
+
nil
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# @return [String, nil] The branch code part of the IBAN, nil if not applicable.
|
257
|
+
def branch_code
|
258
|
+
if @specification && @specification.branch_position_from && @specification.branch_position_to
|
259
|
+
@compact[@specification.branch_position_from..@specification.branch_position_to]
|
260
|
+
else
|
261
|
+
nil
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# @return [String] The account code part of the IBAN.
|
266
|
+
def account_code
|
267
|
+
@compact[((@specification.branch_position_to || @specification.bank_position_to || 3)+1)..-1]
|
268
|
+
end
|
269
|
+
|
270
|
+
# @return [true, false] Whether the country of the IBAN is valid.
|
271
|
+
def valid_country?
|
272
|
+
@specification ? true : false
|
273
|
+
end
|
274
|
+
|
275
|
+
# @return [true, false] Whether the format of the IBAN is valid.
|
276
|
+
def valid_format?
|
277
|
+
specification && specification.iban_regex =~ @compact ? true : false
|
278
|
+
end
|
279
|
+
|
280
|
+
# @return [true, false] Whether the length of the IBAN is valid.
|
281
|
+
def valid_length?
|
282
|
+
specification && @compact.size == specification.iban_length ? true : false
|
283
|
+
end
|
284
|
+
|
285
|
+
# @return [true, false] Whether the checksum of the IBAN is valid.
|
286
|
+
def valid_checksum?
|
287
|
+
numeric % 97 == 1
|
288
|
+
end
|
289
|
+
|
290
|
+
# See Object#<=>
|
291
|
+
#
|
292
|
+
# @return [-1, 0, 1, nil]
|
293
|
+
def <=>(other)
|
294
|
+
other.respond_to?(:compact) ? @compact <=> other.compact : nil
|
295
|
+
end
|
296
|
+
|
297
|
+
# Requires that the checksum digits were left as '??', replaces them with
|
298
|
+
# the proper checksum.
|
299
|
+
#
|
300
|
+
# @return [self]
|
301
|
+
def update_checksum!
|
302
|
+
raise "Checksum digit placeholders missing" unless @compact[2,2] == '??'
|
303
|
+
|
304
|
+
@compact[2,2] = calculated_check_digits
|
305
|
+
|
306
|
+
self
|
307
|
+
end
|
308
|
+
|
309
|
+
# @return [String] The check-digits as calculated from the IBAN.
|
310
|
+
def calculated_check_digits
|
311
|
+
"%02d" % (98-(self.class.numerify(bban+@country)*100)%97)
|
312
|
+
end
|
313
|
+
|
314
|
+
# See Object#inspect
|
315
|
+
def inspect
|
316
|
+
sprintf "#<%p %s>", self.class, formatted
|
317
|
+
end
|
318
|
+
|
319
|
+
# @return [String] The compact form of the IBAN as a String.
|
320
|
+
def to_s
|
321
|
+
@compact.dup
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ISO
|
4
|
+
class IBAN
|
5
|
+
|
6
|
+
# Specification of the IBAN format for one country. Every country has its
|
7
|
+
# own specification of the IBAN format.
|
8
|
+
# SWIFT is the official authority where those formats are registered.
|
9
|
+
class Specification
|
10
|
+
|
11
|
+
# A mapping from SWIFT structure specification to PCRE regex.
|
12
|
+
StructureCodes = {
|
13
|
+
'n' => '\d',
|
14
|
+
'a' => '[A-Z]',
|
15
|
+
'c' => '[A-Za-z0-9]',
|
16
|
+
'e' => ' ',
|
17
|
+
}
|
18
|
+
|
19
|
+
# Load the specifications YAML.
|
20
|
+
#
|
21
|
+
# @return [Hash<String => ISO::IBAN::Specification>]
|
22
|
+
def self.load_yaml(path)
|
23
|
+
Hash[YAML.load_file(path).map { |country, spec| [country, new(*spec)] }]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Parse the SWIFT provided file (which sadly is a mess).
|
27
|
+
#
|
28
|
+
# @return [Array<ISO::IBAN::Specification>] an array with all specifications.
|
29
|
+
def self.parse_file(path)
|
30
|
+
File.read(path, encoding: Encoding::Windows_1252).encode(Encoding::UTF_8).split("\r\n").tap(&:shift).flat_map { |line|
|
31
|
+
country_name, country_codes, iban_structure, iban_length, bban_structure, bban_length, bank_position = line.split(/\t/).values_at(0,1,11,12,4,5,6)
|
32
|
+
codes = country_codes.size == 2 ? [country_codes] : country_codes.scan(/\b[A-Z]{2}\b/)
|
33
|
+
bank_position_from, bank_position_to, branch_position_from, branch_position_to = bank_position.match(/(?:[Pp]ositions?|) (\d+)-(\d+)(?:.*Branch identifier positions?: (\d+)-(\d+))?/).captures.map { |pos| pos && pos.to_i+3 }
|
34
|
+
|
35
|
+
codes.map { |a2_country_code|
|
36
|
+
new(
|
37
|
+
country_name.strip,
|
38
|
+
a2_country_code,
|
39
|
+
iban_structure.strip,
|
40
|
+
iban_length.to_i,
|
41
|
+
bban_structure.strip,
|
42
|
+
bban_length.to_i,
|
43
|
+
bank_position_from,
|
44
|
+
bank_position_to,
|
45
|
+
branch_position_from,
|
46
|
+
branch_position_to
|
47
|
+
)
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# *n: Digits (numeric characters 0 to 9 only)
|
53
|
+
# *a: Upper case letters (alphabetic characters A-Z only)
|
54
|
+
# *c: upper and lower case alphanumeric characters (A-Z, a-z and 0-9)
|
55
|
+
# *e: blank space
|
56
|
+
# *nn!: fixed length
|
57
|
+
# *nn: maximum length
|
58
|
+
#
|
59
|
+
# Example: "AL2!n8!n16!c"
|
60
|
+
def self.structure_regex(structure, anchored=true)
|
61
|
+
source = structure.scan(/([A-Z]+)|(\d+)(!?)([nac])/).map { |exact, length, fixed, code|
|
62
|
+
if exact
|
63
|
+
Regexp.escape(exact)
|
64
|
+
else
|
65
|
+
StructureCodes[code]+(fixed ? "{#{length}}" : "{,#{length}}")
|
66
|
+
end
|
67
|
+
}.join('')
|
68
|
+
|
69
|
+
anchored ? /\A#{source}\z/ : /#{source}/
|
70
|
+
end
|
71
|
+
|
72
|
+
attr_reader :country_name,
|
73
|
+
:a2_country_code,
|
74
|
+
:iban_structure,
|
75
|
+
:iban_length,
|
76
|
+
:bban_structure,
|
77
|
+
:bban_length,
|
78
|
+
:bank_position_from,
|
79
|
+
:bank_position_to,
|
80
|
+
:branch_position_from,
|
81
|
+
:branch_position_to
|
82
|
+
|
83
|
+
def initialize(country_name, a2_country_code, iban_structure, iban_length, bban_structure, bban_length, bank_position_from, bank_position_to, branch_position_from, branch_position_to)
|
84
|
+
@country_name = country_name
|
85
|
+
@a2_country_code = a2_country_code
|
86
|
+
@iban_structure = iban_structure
|
87
|
+
@iban_length = iban_length
|
88
|
+
@bban_structure = bban_structure
|
89
|
+
@bban_length = bban_length
|
90
|
+
@bank_position_from = bank_position_from
|
91
|
+
@bank_position_to = bank_position_to
|
92
|
+
@branch_position_from = branch_position_from
|
93
|
+
@branch_position_to = branch_position_to
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [Regexp] A regex to verify the structure of the IBAN.
|
97
|
+
def iban_regex
|
98
|
+
@iban_regex ||= self.class.structure_regex(@iban_structure)
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [Regexp] A regex to identify the structure of the IBAN, without anchors.
|
102
|
+
def unanchored_iban_regex
|
103
|
+
self.class.structure_regex(@iban_structure, false)
|
104
|
+
end
|
105
|
+
|
106
|
+
# @return [Array<Integer>] An array with the lengths of all components.
|
107
|
+
def component_lengths
|
108
|
+
@bban_structure.scan(/\d+/).map(&:to_i)
|
109
|
+
end
|
110
|
+
|
111
|
+
# @return [Array] An array with the Specification properties. Used for serialization.
|
112
|
+
def to_a
|
113
|
+
[
|
114
|
+
@country_name,
|
115
|
+
@a2_country_code,
|
116
|
+
@iban_structure,
|
117
|
+
@iban_length,
|
118
|
+
@bban_structure,
|
119
|
+
@bban_length,
|
120
|
+
@bank_position_from,
|
121
|
+
@bank_position_to,
|
122
|
+
@branch_position_from,
|
123
|
+
@branch_position_to,
|
124
|
+
]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|