codabel 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/LICENSE.md +20 -0
- data/README.md +72 -0
- data/Rakefile +11 -0
- data/lib/codabel/column.rb +35 -0
- data/lib/codabel/error.rb +5 -0
- data/lib/codabel/file.rb +49 -0
- data/lib/codabel/model/account.rb +45 -0
- data/lib/codabel/model.rb +5 -0
- data/lib/codabel/record/header.rb +23 -0
- data/lib/codabel/record/movement.rb +153 -0
- data/lib/codabel/record/movement21.rb +37 -0
- data/lib/codabel/record/movement22.rb +24 -0
- data/lib/codabel/record/movement23.rb +19 -0
- data/lib/codabel/record/movement31.rb +20 -0
- data/lib/codabel/record/movement32.rb +17 -0
- data/lib/codabel/record/movement33.rb +17 -0
- data/lib/codabel/record/new_balance.rb +27 -0
- data/lib/codabel/record/old_balance.rb +20 -0
- data/lib/codabel/record/trailer.rb +73 -0
- data/lib/codabel/record.rb +107 -0
- data/lib/codabel/type/account_and_currency.rb +31 -0
- data/lib/codabel/type/account_description.rb +10 -0
- data/lib/codabel/type/account_structure.rb +19 -0
- data/lib/codabel/type/amount.rb +11 -0
- data/lib/codabel/type/amount_sign.rb +9 -0
- data/lib/codabel/type/an.rb +12 -0
- data/lib/codabel/type/blank.rb +9 -0
- data/lib/codabel/type/communication.rb +37 -0
- data/lib/codabel/type/communication_type.rb +43 -0
- data/lib/codabel/type/date.rb +14 -0
- data/lib/codabel/type/duplicate.rb +10 -0
- data/lib/codabel/type/flag.rb +15 -0
- data/lib/codabel/type/holder.rb +15 -0
- data/lib/codabel/type/n.rb +12 -0
- data/lib/codabel/type/return_transaction_type.rb +29 -0
- data/lib/codabel/type.rb +28 -0
- data/lib/codabel/version.rb +3 -0
- data/lib/codabel.rb +11 -0
- data/tasks/gem.rake +16 -0
- data/tasks/test.rake +17 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d9d1a4c989682a9479310e83a0e81e22f1d8a22b0d8d7b9f08dc348a12264f2d
|
4
|
+
data.tar.gz: 91c1bdbd716b4e27d1091a84bcc95be97910c4d79306ded78f276f1f6b0656c4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c6907d15a4624653e8522f053d7daa4ff6c68c4d25a047876fab664330c1f1b03294dec005d5c604ff1f1a4e5c4de48b0bdd77f3725026215aa39fd82ea40f60
|
7
|
+
data.tar.gz: 5e5ed839bd40d27d7c7c495107aef8bd7c5b4e51980c843934e8509d45c3731de430e00088712360c9ca66a7f756a5e5a4de9e2dbd7bd18d517489a9dadb7bcc
|
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2021 - Flexio SRL
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Codabel - Generate CODA files with ease
|
2
|
+
|
3
|
+
This gem allows generating CODA files (belgian financial format) from structured
|
4
|
+
data. We currently support version 2.6 of the Febelfin specification:
|
5
|
+
|
6
|
+
- https://www.febelfin.be/sites/default/files/2019-04/standard-coda-2.6-en.pdf
|
7
|
+
|
8
|
+
## Features
|
9
|
+
|
10
|
+
* Support for CODA records 0, 1, 2.1, 2.2, 2.3, 3.1, 3.2, 3.3, 8 and 9.
|
11
|
+
* Easy CODA generation from a high-level data model
|
12
|
+
* Access for fine-grained details through detailed record data
|
13
|
+
* Automatical split of mouvements to records 2.x and 3.x
|
14
|
+
* Automatic generation of next and link codes
|
15
|
+
* Automatic generation of trailer record details
|
16
|
+
* Basic check of balances and trailer record (debit, credit, record count)
|
17
|
+
|
18
|
+
### Limitations
|
19
|
+
|
20
|
+
* No support for free information records (4)
|
21
|
+
* No high-level data model for transaction codes (record 2.1)
|
22
|
+
* No high-level data model for R-transaction and reason (record 2.2)
|
23
|
+
* No high-level data model for purpose and category purpose (record 2.2)
|
24
|
+
|
25
|
+
## Example
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
today = Date.parse('2021-11-18')
|
29
|
+
file = Codabel::File.new
|
30
|
+
file << Codabel::Record.header(
|
31
|
+
creation_date: today
|
32
|
+
)
|
33
|
+
file << Codabel::Record.old_balance(
|
34
|
+
balance_date: today,
|
35
|
+
balance: 1765
|
36
|
+
)
|
37
|
+
file << Codabel::Record.movement(
|
38
|
+
amount: 107980, # amounts are always in cents
|
39
|
+
entry_date: today,
|
40
|
+
value_date: today,
|
41
|
+
communication: {
|
42
|
+
structured: '121204102125'
|
43
|
+
}
|
44
|
+
)
|
45
|
+
file << Codabel::Record.movement(
|
46
|
+
amount: -6789,
|
47
|
+
entry_date: today,
|
48
|
+
value_date: today,
|
49
|
+
communication: {
|
50
|
+
unstructured: 'Buying flowers'
|
51
|
+
}
|
52
|
+
)
|
53
|
+
file << Codabel::Record.new_balance(
|
54
|
+
balance_date: today,
|
55
|
+
balance: 1765 + 107980 - 6789
|
56
|
+
)
|
57
|
+
file << Codabel::Record.trailer
|
58
|
+
puts file.to_coda
|
59
|
+
```
|
60
|
+
|
61
|
+
## Licence
|
62
|
+
|
63
|
+
Codabel is distributed by Flexio (https://flexio.app) under a MIT licence
|
64
|
+
(see LICENCE.md)
|
65
|
+
|
66
|
+
## Contribute
|
67
|
+
|
68
|
+
* Fork the library
|
69
|
+
* Add the features that you want together with the unit and integration tests
|
70
|
+
* Please double check that you don't introduce a broken API without reason
|
71
|
+
* Open a pull request and relax!
|
72
|
+
* Don't hesitate to ping the maintainer by email (bernard, you know, at flexio.app)
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Column
|
3
|
+
def initialize(range, path, type, options)
|
4
|
+
@range = range
|
5
|
+
@path = Array(path)
|
6
|
+
@type = type
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
attr_reader :range
|
10
|
+
attr_reader :path
|
11
|
+
attr_reader :type
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
def length
|
15
|
+
range.size
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_coda(record)
|
19
|
+
value = record.data.dig(*path) unless path.empty?
|
20
|
+
value = options[:default] if value.nil?
|
21
|
+
type.to_coda(value, length)
|
22
|
+
end
|
23
|
+
|
24
|
+
def path_starts_with?(prefix)
|
25
|
+
prefix = Array(prefix)
|
26
|
+
@path[0...prefix.size] == prefix
|
27
|
+
end
|
28
|
+
|
29
|
+
def specifics?(data)
|
30
|
+
return false if path.empty?
|
31
|
+
|
32
|
+
!!data.dig(*path)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/codabel/file.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Codabel
|
2
|
+
class File
|
3
|
+
def initialize(records = [])
|
4
|
+
@records = records
|
5
|
+
end
|
6
|
+
attr_reader :records
|
7
|
+
|
8
|
+
def <<(record)
|
9
|
+
@records << record
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_records(type)
|
13
|
+
@records.filter { |record| record.is_a?(type) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_record(type)
|
17
|
+
find_records(type).first
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate!
|
21
|
+
with_actual_records._validate!
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_coda
|
25
|
+
with_actual_records.auto_enrich._validate!._to_coda
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def with_actual_records
|
31
|
+
File.new(@records.map { |record| record.actual_records(self) }.flatten)
|
32
|
+
end
|
33
|
+
|
34
|
+
def auto_enrich
|
35
|
+
File.new(@records.map { |record| record.auto_enrich(self) }.flatten)
|
36
|
+
end
|
37
|
+
|
38
|
+
def _validate!
|
39
|
+
@records.each { |record| record.validate!(self) }
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def _to_coda
|
44
|
+
@records.each_with_object('') do |record, memo|
|
45
|
+
memo << record.to_coda << "\n"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Model
|
3
|
+
class Account < Model
|
4
|
+
BELGIAN_BBAN = :belgian_bban
|
5
|
+
FOREIGN_BBAN = :foreign_bban
|
6
|
+
BELGIAN_IBAN = :belgian_iban
|
7
|
+
FOREIGN_IBAN = :foreign_iban
|
8
|
+
|
9
|
+
def self.dress(value)
|
10
|
+
case value
|
11
|
+
when Account then value
|
12
|
+
when String then new(number: value)
|
13
|
+
when Hash then new(value)
|
14
|
+
else
|
15
|
+
raise ArgumentError, "Unable to dress `#{value}` as an Account"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def currency
|
20
|
+
@currency ||= (super || 'EUR').to_s.gsub(/\s/, '')
|
21
|
+
end
|
22
|
+
|
23
|
+
def number
|
24
|
+
@number ||= super.to_s.gsub(/\s/, '')
|
25
|
+
end
|
26
|
+
|
27
|
+
def structure
|
28
|
+
@structure ||= super || infer_structure
|
29
|
+
end
|
30
|
+
|
31
|
+
def infer_structure
|
32
|
+
case number.to_s.strip
|
33
|
+
when /^BE\d{14}$/
|
34
|
+
BELGIAN_IBAN
|
35
|
+
when /^[A-Z]{2}\d{2}\d{12,30}/
|
36
|
+
FOREIGN_IBAN
|
37
|
+
when /^\d{12}$/
|
38
|
+
BELGIAN_BBAN
|
39
|
+
else
|
40
|
+
FOREIGN_BBAN
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Record
|
3
|
+
class Header < Record
|
4
|
+
column 1..1, nil, Type::N, default: 0
|
5
|
+
column 2..5, nil, Type::N, default: 0
|
6
|
+
column 6..11, :creation_date, Type::Date, default: Date.today
|
7
|
+
column 12..14, :bank_identifier, Type::N, default: 0
|
8
|
+
column 15..16, :application_code, Type::N, default: 5
|
9
|
+
column 17..17, :duplicate, Type::Duplicate, default: false
|
10
|
+
column 18..24, nil, Type::Blank
|
11
|
+
column 25..34, :file_reference, Type::AN, default: ''
|
12
|
+
column 35..60, :addressee_name, Type::AN, default: ''
|
13
|
+
column 61..71, [:bank, :bic], Type::AN, default: ''
|
14
|
+
column 72..82, :holder_identifier, Type::Holder, default: ''
|
15
|
+
column 83..83, nil, Type::Blank
|
16
|
+
column 84..88, :distinct_app_code, Type::N, default: 0
|
17
|
+
column 89..104, :transaction_reference, Type::AN, default: ''
|
18
|
+
column 105..120, :related_reference, Type::AN, default: ''
|
19
|
+
column 121..127, nil, Type::Blank
|
20
|
+
column 128..128, :version_code, Type::N, default: 2
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Record
|
3
|
+
class Movement < Record
|
4
|
+
def to_coda
|
5
|
+
actual_movements.map(&:to_coda).join("\n")
|
6
|
+
end
|
7
|
+
|
8
|
+
def actual_records(_file)
|
9
|
+
actual_movements
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def actual_movements
|
15
|
+
movements = []
|
16
|
+
movements << Movement21.new(data_21)
|
17
|
+
movements << Movement22.new(data_22) if data_22_required?
|
18
|
+
movements << Movement23.new(data_23) if data_23_required?
|
19
|
+
movements << Movement31.new(data_31) if data_31_required?
|
20
|
+
movements << Movement32.new(data_32) if data_32_required?
|
21
|
+
movements << Movement33.new(data_33) if data_33_required?
|
22
|
+
movements
|
23
|
+
end
|
24
|
+
|
25
|
+
def data_21
|
26
|
+
next_code = data_22_required? || data_23_required?
|
27
|
+
data.merge(
|
28
|
+
communication: communication(2, 0, Movement21::COMM_LENGTH),
|
29
|
+
next_code: next_code,
|
30
|
+
link_code: !next_code && data_31_required?
|
31
|
+
).compact
|
32
|
+
end
|
33
|
+
|
34
|
+
def data_22_required?
|
35
|
+
return true if Movement22.required?(data, ignore: Movement22::SHARED)
|
36
|
+
|
37
|
+
!!communication_22
|
38
|
+
end
|
39
|
+
|
40
|
+
def communication_22
|
41
|
+
communication(2, Movement21::COMM_LENGTH, Movement22::COMM_LENGTH)
|
42
|
+
end
|
43
|
+
|
44
|
+
def data_22
|
45
|
+
next_code = data_23_required?
|
46
|
+
data.merge(
|
47
|
+
communication: communication_22,
|
48
|
+
next_code: next_code,
|
49
|
+
link_code: !next_code && data_31_required?
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def data_23_required?
|
54
|
+
return true if Movement23.required?(data, ignore: Movement23::SHARED)
|
55
|
+
|
56
|
+
!!communication_23
|
57
|
+
end
|
58
|
+
|
59
|
+
def communication_23
|
60
|
+
communication(2, Movement21::COMM_LENGTH + Movement22::COMM_LENGTH, Movement23::COMM_LENGTH)
|
61
|
+
end
|
62
|
+
|
63
|
+
def data_23
|
64
|
+
data.merge(
|
65
|
+
communication: communication_23,
|
66
|
+
link_code: data_31_required?
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def data_31_required?
|
71
|
+
return false unless normalized_communication[:structured]
|
72
|
+
return false unless normalized_communication[:unstructured]
|
73
|
+
|
74
|
+
!normalized_communication[:unstructured].empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
def communication_31
|
78
|
+
communication(3, 0, Movement31::COMM_LENGTH)
|
79
|
+
end
|
80
|
+
|
81
|
+
def data_31
|
82
|
+
data.merge(
|
83
|
+
communication: communication_31,
|
84
|
+
next_code: data_32_required? || data_33_required?
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
def data_32_required?
|
89
|
+
return false unless normalized_communication[:structured]
|
90
|
+
return false unless normalized_communication[:unstructured]
|
91
|
+
|
92
|
+
!!communication_32
|
93
|
+
end
|
94
|
+
|
95
|
+
def communication_32
|
96
|
+
communication(3, Movement31::COMM_LENGTH, Movement32::COMM_LENGTH)
|
97
|
+
end
|
98
|
+
|
99
|
+
def data_32
|
100
|
+
data.merge(
|
101
|
+
communication: communication_32,
|
102
|
+
next_code: data_33_required?
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
def data_33_required?
|
107
|
+
return false unless normalized_communication[:structured]
|
108
|
+
return false unless normalized_communication[:unstructured]
|
109
|
+
|
110
|
+
!!communication_33
|
111
|
+
end
|
112
|
+
|
113
|
+
def communication_33
|
114
|
+
communication(3, Movement31::COMM_LENGTH + Movement32::COMM_LENGTH, Movement33::COMM_LENGTH)
|
115
|
+
end
|
116
|
+
|
117
|
+
def data_33
|
118
|
+
data.merge(communication: communication_33)
|
119
|
+
end
|
120
|
+
|
121
|
+
def communication(level, from, max_length)
|
122
|
+
communication = normalized_communication
|
123
|
+
which = if level == 2
|
124
|
+
communication[:structured] ? :structured : :unstructured
|
125
|
+
else
|
126
|
+
:unstructured
|
127
|
+
end
|
128
|
+
communication = communication.slice(which)
|
129
|
+
communication[which] = communication[which][from...(from + max_length)].to_s
|
130
|
+
communication[which] = nil if communication[which].empty?
|
131
|
+
communication = communication.compact
|
132
|
+
communication.empty? ? nil : communication
|
133
|
+
end
|
134
|
+
|
135
|
+
def normalized_communication
|
136
|
+
@normalized_communication ||= begin
|
137
|
+
communication = data[:communication]
|
138
|
+
case communication
|
139
|
+
when NilClass
|
140
|
+
{ unstructured: '' }
|
141
|
+
when String
|
142
|
+
{ unstructured: communication }
|
143
|
+
when Hash
|
144
|
+
communication = { unstructured: '' } if communication.empty?
|
145
|
+
communication
|
146
|
+
else
|
147
|
+
check!(false, "Unexpected communication #{communication}")
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Record
|
3
|
+
class Movement21 < Record
|
4
|
+
COMM_LENGTH = 1 + (115 - 63)
|
5
|
+
|
6
|
+
column 1..1, nil, Type::N, default: 2
|
7
|
+
column 2..2, nil, Type::N, default: 1
|
8
|
+
column 3..6, :sequence_number, Type::N, default: 1
|
9
|
+
column 7..10, :detail_number, Type::N, default: 0
|
10
|
+
column 11..31, :bank_reference, Type::AN, default: ''
|
11
|
+
column 32..32, :amount, Type::AmountSign, default: 0
|
12
|
+
column 33..47, :amount, Type::Amount, default: 0
|
13
|
+
column 48..53, :value_date, Type::Date, default: nil
|
14
|
+
column 54..61, :transaction_code, Type::N, default: 0
|
15
|
+
column 62..62, :communication, Type::CommunicationType, default: ''
|
16
|
+
column 63..115, :communication, Type::Communication, default: ''
|
17
|
+
column 116..121, :entry_date, Type::Date, default: Date.today
|
18
|
+
column 122..124, :sequence_number_paper, Type::N, default: 0
|
19
|
+
column 125..125, :globalisation_code, Type::N, default: 0
|
20
|
+
column 126..126, :next_code, Type::Flag, default: false
|
21
|
+
column 127..127, nil, Type::Blank, default: ''
|
22
|
+
column 128..128, :link_code, Type::Flag, default: false
|
23
|
+
|
24
|
+
def amount
|
25
|
+
data[:amount] || 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def debit_amount
|
29
|
+
data[:amount] <= 0 ? data[:amount] : 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def credit_amount
|
33
|
+
data[:amount] > 0 ? data[:amount] : 0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Record
|
3
|
+
class Movement22 < Record
|
4
|
+
COMM_LENGTH = 1 + (63 - 11)
|
5
|
+
SHARED = %i[sequence_number detail_number communication next_code link_code].freeze
|
6
|
+
|
7
|
+
column 1..1, nil, Type::N, default: 2
|
8
|
+
column 2..2, nil, Type::N, default: 2
|
9
|
+
column 3..6, :sequence_number, Type::N, default: 1
|
10
|
+
column 7..10, :detail_number, Type::N, default: 0
|
11
|
+
column 11..63, :communication, Type::Communication, default: ''
|
12
|
+
column 64..98, %i[counterparty reference], Type::AN, default: ''
|
13
|
+
column 99..109, %i[counterparty bank bic], Type::AN, default: ''
|
14
|
+
column 110..112, nil, Type::Blank, default: ''
|
15
|
+
column 113..113, :return_transaction_type, Type::ReturnTransactionType, default: ''
|
16
|
+
column 114..117, :reason_return_code, Type::AN, default: nil
|
17
|
+
column 118..121, :purpose_category, Type::AN, default: ''
|
18
|
+
column 122..125, :purpose, Type::AN, default: ''
|
19
|
+
column 126..126, :next_code, Type::Flag, default: false
|
20
|
+
column 127..127, nil, Type::Blank, default: ''
|
21
|
+
column 128..128, :link_code, Type::Flag, default: false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Record
|
3
|
+
class Movement23 < Record
|
4
|
+
COMM_LENGTH = 1 + (125 - 83)
|
5
|
+
SHARED = %i[sequence_number detail_number communication link_code].freeze
|
6
|
+
|
7
|
+
column 1..1, nil, Type::N, default: 2
|
8
|
+
column 2..2, nil, Type::N, default: 3
|
9
|
+
column 3..6, :sequence_number, Type::N, default: 1
|
10
|
+
column 7..10, :detail_number, Type::N, default: 0
|
11
|
+
column 11..47, %i[counterparty account], Type::AccountAndCurrency, default: ''
|
12
|
+
column 48..82, %i[counterparty name], Type::AN, default: ''
|
13
|
+
column 83..125, :communication, Type::Communication, default: ''
|
14
|
+
column 126..126, nil, Type::N, default: 0
|
15
|
+
column 127..127, nil, Type::Blank, default: ''
|
16
|
+
column 128..128, :link_code, Type::Flag, default: false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Record
|
3
|
+
class Movement31 < Record
|
4
|
+
COMM_LENGTH = 1 + (113 - 41)
|
5
|
+
|
6
|
+
column 1..1, nil, Type::N, default: 3
|
7
|
+
column 2..2, nil, Type::N, default: 1
|
8
|
+
column 3..6, :sequence_number, Type::N, default: 1
|
9
|
+
column 7..10, :detail_number, Type::N, default: 0
|
10
|
+
column 11..31, :bank_reference, Type::AN, default: ''
|
11
|
+
column 32..39, :transaction_code, Type::N, default: 0
|
12
|
+
column 40..40, :communication, Type::CommunicationType, default: ''
|
13
|
+
column 41..113, :communication, Type::Communication, default: ''
|
14
|
+
column 114..125, nil, Type::Blank
|
15
|
+
column 126..126, :next_code, Type::Flag, default: false
|
16
|
+
column 127..127, nil, Type::Blank, default: ''
|
17
|
+
column 128..128, :link_code, Type::Flag, default: false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Record
|
3
|
+
class Movement32 < Record
|
4
|
+
COMM_LENGTH = 1 + (115 - 11)
|
5
|
+
|
6
|
+
column 1..1, nil, Type::N, default: 3
|
7
|
+
column 2..2, nil, Type::N, default: 2
|
8
|
+
column 3..6, :sequence_number, Type::N, default: 1
|
9
|
+
column 7..10, :detail_number, Type::N, default: 0
|
10
|
+
column 11..115, :communication, Type::Communication, default: ''
|
11
|
+
column 116..125, nil, Type::Blank
|
12
|
+
column 126..126, :next_code, Type::Flag, default: false
|
13
|
+
column 127..127, nil, Type::Blank, default: ''
|
14
|
+
column 128..128, :link_code, Type::Flag, default: false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Record
|
3
|
+
class Movement33 < Record
|
4
|
+
COMM_LENGTH = 1 + (100 - 11)
|
5
|
+
|
6
|
+
column 1..1, nil, Type::N, default: 3
|
7
|
+
column 2..2, nil, Type::N, default: 3
|
8
|
+
column 3..6, :sequence_number, Type::N, default: 1
|
9
|
+
column 7..10, :detail_number, Type::N, default: 0
|
10
|
+
column 11..100, :communication, Type::Communication, default: ''
|
11
|
+
column 101..125, nil, Type::Blank
|
12
|
+
column 126..126, :next_code, Type::Flag, default: false
|
13
|
+
column 127..127, nil, Type::Blank, default: ''
|
14
|
+
column 128..128, :link_code, Type::Flag, default: false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Record
|
3
|
+
class NewBalance < Record
|
4
|
+
FOLLOWING = { when_true: '1', when_false: '0' }.freeze
|
5
|
+
|
6
|
+
column 1..1, nil, Type::N, default: 8
|
7
|
+
column 2..4, :sequence_number_paper, Type::N, default: 0
|
8
|
+
column 5..41, :account, Type::AccountAndCurrency, default: ''
|
9
|
+
column 42..42, :balance, Type::AmountSign, default: 0
|
10
|
+
column 43..57, :balance, Type::Amount, default: 0
|
11
|
+
column 58..63, :balance_date, Type::Date, default: Date.today
|
12
|
+
column 64..127, nil, Type::AN, default: ''
|
13
|
+
column 128..128, :communication_follows, Type::Flag.new(**FOLLOWING), default: false
|
14
|
+
|
15
|
+
def balance
|
16
|
+
data[:balance] || 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate!(file)
|
20
|
+
return unless (old_balance = file.find_record(OldBalance))
|
21
|
+
|
22
|
+
expected = old_balance.balance + file.find_records(Movement21).map(&:amount).sum
|
23
|
+
check!(expected == balance, "Invalid new balance: expected #{expected}, got #{balance}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Codabel
|
2
|
+
class Record
|
3
|
+
class OldBalance < Record
|
4
|
+
column 1..1, nil, Type::N, default: 1
|
5
|
+
column 2..2, :account, Type::AccountStructure, default: 2
|
6
|
+
column 3..5, :sequence_number_paper, Type::N, default: 0
|
7
|
+
column 6..42, :account, Type::AccountAndCurrency, default: ''
|
8
|
+
column 43..43, :balance, Type::AmountSign, default: 0
|
9
|
+
column 44..58, :balance, Type::Amount, default: 0
|
10
|
+
column 59..64, :balance_date, Type::Date, default: Date.today
|
11
|
+
column 65..90, [:account, :holder_name], Type::AN, default: ''
|
12
|
+
column 91..125, :account, Type::AccountDescription, default: ''
|
13
|
+
column 126..128, :sequence_number, Type::N, default: 1
|
14
|
+
|
15
|
+
def balance
|
16
|
+
data[:balance] || 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|