codabel 1.0.0
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 +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
|