cfonb 0.0.0 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cfonb/error.rb +15 -0
- data/lib/cfonb/line_parser/base.rb +82 -0
- data/lib/cfonb/line_parser/new_balance.rb +15 -0
- data/lib/cfonb/line_parser/operation.rb +24 -0
- data/lib/cfonb/line_parser/operation_detail.rb +18 -0
- data/lib/cfonb/line_parser/previous_balance.rb +15 -0
- data/lib/cfonb/line_parser.rb +21 -0
- data/lib/cfonb/operation.rb +54 -0
- data/lib/cfonb/operation_detail/fee.rb +19 -0
- data/lib/cfonb/operation_detail/lc2.rb +13 -0
- data/lib/cfonb/operation_detail/lcc.rb +13 -0
- data/lib/cfonb/operation_detail/lcs.rb +13 -0
- data/lib/cfonb/operation_detail/lib.rb +13 -0
- data/lib/cfonb/operation_detail/mmo.rb +22 -0
- data/lib/cfonb/operation_detail/nbe.rb +15 -0
- data/lib/cfonb/operation_detail/npy.rb +15 -0
- data/lib/cfonb/operation_detail/rcn.rb +20 -0
- data/lib/cfonb/operation_detail/ref.rb +18 -0
- data/lib/cfonb/operation_detail.rb +21 -0
- data/lib/cfonb/parser.rb +82 -0
- data/lib/cfonb/refinements/strings.rb +18 -0
- data/lib/cfonb/statement.rb +59 -0
- data/lib/cfonb.rb +33 -0
- data/spec/cfonb/line_parser/new_balance_spec.rb +26 -0
- data/spec/cfonb/line_parser/operation_detail_spec.rb +29 -0
- data/spec/cfonb/line_parser/operation_spec.rb +35 -0
- data/spec/cfonb/line_parser/previous_balance_spec.rb +26 -0
- data/spec/cfonb/operation_spec.rb +184 -0
- data/spec/cfonb/parser_spec.rb +469 -0
- data/spec/cfonb/statement_spec.rb +61 -0
- metadata +33 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1666b9858511859d77ab8b3c14a11ee214548b5da5154ee927cf7e51b14eca95
|
4
|
+
data.tar.gz: 292ed95e4250414dc82ef2967df62663cdadf529d72d8229d5609eee0fb3f0b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c74e319b7448e79163010bea1efeb5a5c5391bd0352b3c38d3145fd41f27ae60b0fe562d25b65698d207418288fb61dc4d49231be968f7dead6905bbc854132
|
7
|
+
data.tar.gz: a568a83590cfd3666d9ac96ba4dd6c09a39e73aa3a8136904cc24fe25df7d6b5b2f6d3fac74aee01e4ad57bf4455f9d4cf9e55ed993ca8f07f4b440eb103dc8d
|
data/lib/cfonb/error.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class ParserError < Error; end
|
7
|
+
|
8
|
+
class InvalidCodeError < ParserError; end
|
9
|
+
|
10
|
+
class UnstartedStatementError < ParserError; end
|
11
|
+
|
12
|
+
class UnstartedOperationError < ParserError; end
|
13
|
+
|
14
|
+
class UnfinishedStatementError < ParserError; end
|
15
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
5
|
+
module CFONB
|
6
|
+
module LineParser
|
7
|
+
class Base
|
8
|
+
using CFONB::Refinements::Strings
|
9
|
+
|
10
|
+
BASE_DICTIONARY = [
|
11
|
+
['code', (0..1)].freeze,
|
12
|
+
['bank', (2..6)],
|
13
|
+
['branch', (11..15)],
|
14
|
+
['currency', (16..18)],
|
15
|
+
['scale', 19, proc { _1.to_i }],
|
16
|
+
['account', (21..31)],
|
17
|
+
['date', (34..39), proc { |value, instance| instance.send(:parse_date, value) }]
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
AMOUNT_SPECIFIERS = {
|
21
|
+
'A' => { sign: 1, value: '1' },
|
22
|
+
'B' => { sign: 1, value: '2' },
|
23
|
+
'C' => { sign: 1, value: '3' },
|
24
|
+
'D' => { sign: 1, value: '4' },
|
25
|
+
'E' => { sign: 1, value: '5' },
|
26
|
+
'F' => { sign: 1, value: '6' },
|
27
|
+
'G' => { sign: 1, value: '7' },
|
28
|
+
'H' => { sign: 1, value: '8' },
|
29
|
+
'I' => { sign: 1, value: '9' },
|
30
|
+
'{' => { sign: 1, value: '0' },
|
31
|
+
'J' => { sign: -1, value: '1' },
|
32
|
+
'K' => { sign: -1, value: '2' },
|
33
|
+
'L' => { sign: -1, value: '3' },
|
34
|
+
'M' => { sign: -1, value: '4' },
|
35
|
+
'N' => { sign: -1, value: '5' },
|
36
|
+
'O' => { sign: -1, value: '6' },
|
37
|
+
'P' => { sign: -1, value: '7' },
|
38
|
+
'Q' => { sign: -1, value: '8' },
|
39
|
+
'R' => { sign: -1, value: '9' },
|
40
|
+
'}' => { sign: -1, value: '0' }
|
41
|
+
}.transform_values(&:freeze).freeze
|
42
|
+
|
43
|
+
attr_reader :body, *BASE_DICTIONARY.map(&:first)
|
44
|
+
|
45
|
+
def initialize(input)
|
46
|
+
@body = input
|
47
|
+
(BASE_DICTIONARY + self.class::DICTIONARY).each { parse_attribute(*_1) }
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def parse_attribute(attr, position, method = nil)
|
53
|
+
input = body[position]&.strip
|
54
|
+
value = method ? method.call(input, self) : input
|
55
|
+
|
56
|
+
instance_variable_set(:"@#{attr}", value)
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_amount(input)
|
60
|
+
specifier = AMOUNT_SPECIFIERS[input.last]
|
61
|
+
raise ParserError, "Invalid specifier '#{input.last}' for line #{input}" unless specifier
|
62
|
+
|
63
|
+
specifier[:sign] * BigDecimal(input[0..-2] + specifier[:value]) / (10**scale)
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_date(input)
|
67
|
+
# This won't work after 2060
|
68
|
+
return if input.strip.empty?
|
69
|
+
|
70
|
+
input_with_year = if input.last(2).to_i > 60
|
71
|
+
"#{input[0..3]}19#{input[4..5]}"
|
72
|
+
else
|
73
|
+
"#{input[0..3]}20#{input[4..5]}"
|
74
|
+
end
|
75
|
+
|
76
|
+
Date.strptime(input_with_year, '%d%m%Y')
|
77
|
+
rescue Date::Error
|
78
|
+
raise ParserError, "Invalid date '#{input}' for line #{input}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
module LineParser
|
5
|
+
class NewBalance < Base
|
6
|
+
DICTIONARY = [
|
7
|
+
['amount', (90..103), proc { |value, instance| instance.send(:parse_amount, value) }]
|
8
|
+
].freeze
|
9
|
+
|
10
|
+
attr_reader(*DICTIONARY.map(&:first))
|
11
|
+
|
12
|
+
CFONB::LineParser.register(CFONB::Parser::NEW_BALANCE_CODE, self)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
module LineParser
|
5
|
+
class Operation < Base
|
6
|
+
DICTIONARY = [
|
7
|
+
['internal_operation_code', (7..10)],
|
8
|
+
['interbank_operation_code', (32..33)],
|
9
|
+
['rejection_code', (40..41)],
|
10
|
+
['value_date', (42..47), proc { |value, instance| instance.send(:parse_date, value) }],
|
11
|
+
['label', (48..79)],
|
12
|
+
['number', (81..87), proc { _1.to_i }],
|
13
|
+
['exoneration_code', 88],
|
14
|
+
['unavailability_code', 89],
|
15
|
+
['reference', (104..119)],
|
16
|
+
['amount', (90..103), proc { |value, instance| instance.send(:parse_amount, value) }]
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
attr_reader(*DICTIONARY.map(&:first))
|
20
|
+
|
21
|
+
CFONB::LineParser.register(CFONB::Parser::OPERATION_CODE, self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
module LineParser
|
5
|
+
class OperationDetail < Base
|
6
|
+
DICTIONARY = [
|
7
|
+
['internal_operation_code', (7..10)],
|
8
|
+
['interbank_operation_code', (32..33)],
|
9
|
+
['detail_code', (45..47)],
|
10
|
+
['detail', (48..117)]
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
attr_reader(*DICTIONARY.map(&:first))
|
14
|
+
|
15
|
+
CFONB::LineParser.register(CFONB::Parser::OPERATION_DETAIL_CODE, self)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
module LineParser
|
5
|
+
class PreviousBalance < Base
|
6
|
+
DICTIONARY = [
|
7
|
+
['amount', (90..103), proc { |value, instance| instance.send(:parse_amount, value) }]
|
8
|
+
].freeze
|
9
|
+
|
10
|
+
attr_reader(*DICTIONARY.map(&:first))
|
11
|
+
|
12
|
+
CFONB::LineParser.register(CFONB::Parser::PREVIOUS_BALANCE_CODE, self)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
module LineParser
|
5
|
+
using CFONB::Refinements::Strings
|
6
|
+
|
7
|
+
@parsers = {}
|
8
|
+
|
9
|
+
def self.register(code, klass)
|
10
|
+
@parsers[code] = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.for(code)
|
14
|
+
@parsers[code]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.parse(input)
|
18
|
+
self.for(input.first(2)).new(input)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
class Operation
|
5
|
+
BASE_ATTRIBUTES = %i[
|
6
|
+
raw
|
7
|
+
amount
|
8
|
+
currency
|
9
|
+
date
|
10
|
+
exoneration_code
|
11
|
+
interbank_code
|
12
|
+
internal_code
|
13
|
+
label
|
14
|
+
number
|
15
|
+
reference
|
16
|
+
rejection_code
|
17
|
+
unavailability_code
|
18
|
+
value_date
|
19
|
+
].freeze
|
20
|
+
|
21
|
+
attr_accessor(*BASE_ATTRIBUTES)
|
22
|
+
|
23
|
+
def initialize(line)
|
24
|
+
self.raw = line.body
|
25
|
+
self.internal_code = line.internal_operation_code
|
26
|
+
self.interbank_code = line.interbank_operation_code
|
27
|
+
self.rejection_code = line.rejection_code
|
28
|
+
self.exoneration_code = line.exoneration_code
|
29
|
+
self.unavailability_code = line.unavailability_code
|
30
|
+
self.currency = line.currency
|
31
|
+
self.amount = line.amount
|
32
|
+
self.date = line.date
|
33
|
+
self.value_date = line.value_date
|
34
|
+
self.label = line.label.strip
|
35
|
+
self.number = line.number
|
36
|
+
self.reference = line.reference
|
37
|
+
end
|
38
|
+
|
39
|
+
def merge_detail(line)
|
40
|
+
self.raw += "\n#{line.body}"
|
41
|
+
OperationDetail.for(line)&.apply(self, line)
|
42
|
+
end
|
43
|
+
|
44
|
+
def type_code
|
45
|
+
"#{interbank_code}#{direction}"
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def direction
|
51
|
+
amount.positive? ? 'C' : 'D'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
module OperationDetail
|
5
|
+
class FEE
|
6
|
+
ATTRIBUTES = %i[fee fee_currency].freeze
|
7
|
+
|
8
|
+
def self.apply(operation, line)
|
9
|
+
operation.fee_currency = line.detail[0..2]
|
10
|
+
scale = line.detail[3].to_i
|
11
|
+
sign = operation.amount <=> 0 # the detail amount is unsigned
|
12
|
+
|
13
|
+
operation.fee = sign * BigDecimal(line.detail[4..17]) / (10**scale)
|
14
|
+
end
|
15
|
+
|
16
|
+
CFONB::OperationDetail.register('FEE', self)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
5
|
+
module CFONB
|
6
|
+
module OperationDetail
|
7
|
+
class MMO
|
8
|
+
ATTRIBUTES = %i[original_currency original_amount].freeze
|
9
|
+
|
10
|
+
def self.apply(operation, line)
|
11
|
+
operation.original_currency = line.detail[0..2]
|
12
|
+
|
13
|
+
scale = line.detail[3].to_i
|
14
|
+
sign = operation.amount <=> 0 # the detail amount is unsigned
|
15
|
+
|
16
|
+
operation.original_amount = sign * BigDecimal(line.detail[4..17]) / (10**scale)
|
17
|
+
end
|
18
|
+
|
19
|
+
CFONB::OperationDetail.register('MMO', self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
module OperationDetail
|
5
|
+
class NBE
|
6
|
+
ATTRIBUTES = %i[creditor].freeze
|
7
|
+
|
8
|
+
def self.apply(operation, line)
|
9
|
+
operation.creditor = line.detail.strip
|
10
|
+
end
|
11
|
+
|
12
|
+
CFONB::OperationDetail.register('NBE', self)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
module OperationDetail
|
5
|
+
class NPY
|
6
|
+
ATTRIBUTES = %i[debtor].freeze
|
7
|
+
|
8
|
+
def self.apply(operation, line)
|
9
|
+
operation.debtor = line.detail.strip
|
10
|
+
end
|
11
|
+
|
12
|
+
CFONB::OperationDetail.register('NPY', self)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
module OperationDetail
|
5
|
+
class RCN
|
6
|
+
ATTRIBUTES = %i[reference purpose].freeze
|
7
|
+
|
8
|
+
def self.apply(operation, line)
|
9
|
+
operation.reference = [
|
10
|
+
operation.reference,
|
11
|
+
line.detail[0..34].strip
|
12
|
+
].compact.join(' - ')
|
13
|
+
|
14
|
+
operation.purpose = line.detail[35..-1]&.strip
|
15
|
+
end
|
16
|
+
|
17
|
+
CFONB::OperationDetail.register('RCN', self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
module OperationDetail
|
5
|
+
class REF
|
6
|
+
ATTRIBUTES = %i[reference].freeze
|
7
|
+
|
8
|
+
def self.apply(operation, line)
|
9
|
+
operation.reference = [
|
10
|
+
operation.reference,
|
11
|
+
line.detail.strip
|
12
|
+
].compact.join(' - ')
|
13
|
+
end
|
14
|
+
|
15
|
+
CFONB::OperationDetail.register('REF', self)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
module OperationDetail
|
5
|
+
@details = {}
|
6
|
+
|
7
|
+
def self.register(code, klass)
|
8
|
+
if klass.const_defined?(:ATTRIBUTES)
|
9
|
+
Operation.class_eval do
|
10
|
+
attr_accessor(*klass::ATTRIBUTES)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
@details[code] = klass
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.for(line)
|
18
|
+
@details[line.detail_code]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/cfonb/parser.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
class Parser
|
5
|
+
using CFONB::Refinements::Strings
|
6
|
+
|
7
|
+
CODES = [
|
8
|
+
PREVIOUS_BALANCE_CODE = '01',
|
9
|
+
OPERATION_CODE = '04',
|
10
|
+
OPERATION_DETAIL_CODE = '05',
|
11
|
+
NEW_BALANCE_CODE = '07'
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
def initialize(input)
|
15
|
+
@input = input
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse(optimistic: false)
|
19
|
+
@statements = []
|
20
|
+
@current_statement = nil
|
21
|
+
@current_operation = nil
|
22
|
+
@optimistic = optimistic
|
23
|
+
|
24
|
+
each_line { parse_line(_1) }
|
25
|
+
|
26
|
+
statements
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :input, :statements, :current_statement, :current_operation, :optimistic
|
32
|
+
|
33
|
+
def each_line
|
34
|
+
input.each_line do |line|
|
35
|
+
(line.size / 120).times do |index|
|
36
|
+
start = index * 120
|
37
|
+
finish = start + 119
|
38
|
+
|
39
|
+
yield line[start..finish]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_line(line)
|
45
|
+
return if line.strip.empty?
|
46
|
+
raise InvalidCodeError, "Invalid line code '#{line.first(2)}'" unless CODES.include?(line.first(2))
|
47
|
+
|
48
|
+
line = CFONB::LineParser.parse(line)
|
49
|
+
|
50
|
+
case line.code
|
51
|
+
when PREVIOUS_BALANCE_CODE
|
52
|
+
return handle_error(UnfinishedStatementError) if current_statement
|
53
|
+
|
54
|
+
@current_statement = CFONB::Statement.new(line)
|
55
|
+
when OPERATION_CODE
|
56
|
+
return handle_error(UnstartedStatementError) unless current_statement
|
57
|
+
|
58
|
+
current_statement.operations << current_operation if current_operation
|
59
|
+
@current_operation = CFONB::Operation.new(line)
|
60
|
+
when OPERATION_DETAIL_CODE
|
61
|
+
return handle_error(UnstartedOperationError) unless current_operation
|
62
|
+
|
63
|
+
current_operation.merge_detail(line)
|
64
|
+
when NEW_BALANCE_CODE
|
65
|
+
return handle_error(UnstartedStatementError) unless current_statement
|
66
|
+
|
67
|
+
current_statement.operations << current_operation if current_operation
|
68
|
+
current_statement.merge_new_balance(line)
|
69
|
+
statements << current_statement
|
70
|
+
|
71
|
+
@current_statement = nil
|
72
|
+
@current_operation = nil
|
73
|
+
end
|
74
|
+
rescue CFONB::ParserError => e
|
75
|
+
handle_error(e)
|
76
|
+
end
|
77
|
+
|
78
|
+
def handle_error(error)
|
79
|
+
raise error unless optimistic
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CFONB
|
4
|
+
class Statement
|
5
|
+
attr_accessor(
|
6
|
+
*%i[
|
7
|
+
begin_raw end_raw
|
8
|
+
bank branch currency account
|
9
|
+
from from_balance
|
10
|
+
to to_balance
|
11
|
+
operations
|
12
|
+
]
|
13
|
+
)
|
14
|
+
|
15
|
+
def initialize(line)
|
16
|
+
self.begin_raw = line.body
|
17
|
+
self.bank = line.bank
|
18
|
+
self.branch = line.branch
|
19
|
+
self.currency = line.currency
|
20
|
+
self.account = line.account
|
21
|
+
self.from = line.date
|
22
|
+
self.from_balance = line.amount
|
23
|
+
self.operations = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def merge_new_balance(line)
|
27
|
+
self.end_raw = line.body
|
28
|
+
self.to = line.date
|
29
|
+
self.to_balance = line.amount
|
30
|
+
end
|
31
|
+
|
32
|
+
def raw
|
33
|
+
[
|
34
|
+
begin_raw,
|
35
|
+
operations.map(&:raw),
|
36
|
+
end_raw
|
37
|
+
].join("\n")
|
38
|
+
end
|
39
|
+
|
40
|
+
def rib
|
41
|
+
# https://fr.wikipedia.org/wiki/Clé_RIB
|
42
|
+
key = 97 - ((
|
43
|
+
(bank.to_i * 89) +
|
44
|
+
(branch.to_i * 15) +
|
45
|
+
(account.upcase.tr('A-IJ-RS-Z', '1-91-92-9').to_i * 3)
|
46
|
+
) % 97)
|
47
|
+
|
48
|
+
"#{bank}#{branch}#{account}#{key.to_s.rjust(2, '0')}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def iban
|
52
|
+
# https://fr.wikipedia.org/wiki/International_Bank_Account_Number
|
53
|
+
normalized_rib = "#{rib}FR00".upcase.gsub(/[A-Z]/) { _1.ord - 55 }
|
54
|
+
key = 98 - (normalized_rib.to_i % 97)
|
55
|
+
|
56
|
+
"FR#{key.to_s.rjust(2, '0')}#{rib}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/cfonb.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
require_relative 'cfonb/refinements/strings'
|
4
|
+
|
5
|
+
require_relative 'cfonb/error'
|
6
|
+
require_relative 'cfonb/parser'
|
7
|
+
require_relative 'cfonb/statement'
|
8
|
+
require_relative 'cfonb/operation'
|
9
|
+
require_relative 'cfonb/operation_detail'
|
10
|
+
|
11
|
+
require_relative 'cfonb/line_parser'
|
12
|
+
require_relative 'cfonb/line_parser/base'
|
13
|
+
require_relative 'cfonb/line_parser/previous_balance'
|
14
|
+
require_relative 'cfonb/line_parser/operation'
|
15
|
+
require_relative 'cfonb/line_parser/operation_detail'
|
16
|
+
require_relative 'cfonb/line_parser/new_balance'
|
17
|
+
|
18
|
+
require_relative 'cfonb/operation_detail/lib'
|
19
|
+
require_relative 'cfonb/operation_detail/lcc'
|
20
|
+
require_relative 'cfonb/operation_detail/lc2'
|
21
|
+
require_relative 'cfonb/operation_detail/lcs'
|
22
|
+
require_relative 'cfonb/operation_detail/mmo'
|
23
|
+
require_relative 'cfonb/operation_detail/nbe'
|
24
|
+
require_relative 'cfonb/operation_detail/npy'
|
25
|
+
require_relative 'cfonb/operation_detail/rcn'
|
26
|
+
require_relative 'cfonb/operation_detail/ref'
|
27
|
+
require_relative 'cfonb/operation_detail/fee'
|
28
|
+
|
29
|
+
module CFONB
|
30
|
+
def self.parse(input, optimistic: false)
|
31
|
+
Parser.new(input).parse(optimistic: optimistic)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cfonb'
|
4
|
+
|
5
|
+
describe CFONB::LineParser::NewBalance do
|
6
|
+
describe '.initialize' do
|
7
|
+
subject(:line) { described_class.new(input) }
|
8
|
+
|
9
|
+
let(:input) do
|
10
|
+
'0115589 01020EUR2 98765432100 150519 0000000001904L150519160519 '
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'correctly parses a line' do
|
14
|
+
expect(line).to have_attributes(
|
15
|
+
'code' => '01',
|
16
|
+
'bank' => '15589',
|
17
|
+
'branch' => '01020',
|
18
|
+
'currency' => 'EUR',
|
19
|
+
'scale' => 2,
|
20
|
+
'account' => '98765432100',
|
21
|
+
'date' => Date.new(2019, 5, 15),
|
22
|
+
'amount' => -190.43
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|