bai2 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +27 -5
- data/lib/bai2.rb +9 -3
- data/lib/bai2/integrity.rb +31 -17
- data/lib/bai2/record.rb +1 -1
- data/lib/bai2/version.rb +1 -1
- data/test/data/daily_with_summary.bai2 +11 -0
- data/test/data/eod_without_as_of_time.bai2 +17 -0
- data/test/data/invalid_checksum_eod.bai2 +17 -0
- data/test/tests/bai2.rb +23 -5
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 975f734808bc96fce7ca38c08e1ba90e8839b8c6
|
4
|
+
data.tar.gz: 99d17eec7002b0b0c6ca70b9a3228f2500efadc5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdd5385fe33114722442f33838c2bab6406cfc41ab7a1e3125bd09bf1d269e351e57008090da8ccb34b6ae11edefd4ec222cf9ae7fad32edcd3fb86a9b18bb6e
|
7
|
+
data.tar.gz: eb2228fdfaaba4856d141508995809e7ecc0235de2b6437206faa774c9382c469486ae9a001be4a2398746882a697b59936fd01aad5cf1713382159c6eed3ed7
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Changelog
|
2
|
+
|
3
|
+
## 2.0.0
|
4
|
+
- Add parameters for whether or not we use account summaries for account control checksum. We now include it by default. To disable it `account_control_ignores_summary_amounts` should passed into `options` This is the only breaking change.
|
5
|
+
- Allow for optional "as of" times on the Group header.
|
6
|
+
|
7
|
+
## 1.0.0
|
8
|
+
- Initial Release
|
data/README.md
CHANGED
@@ -75,6 +75,26 @@ file.groups.filter {|g| g.destination == YourOrgId }.each do |group|
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
```
|
78
|
+
## Options
|
79
|
+
`Bai2::BaiFile.parse` and `Bai2::BaiFile.new` accept an optional second parameter, `options`.
|
80
|
+
|
81
|
+
* `options[:account_control_ignores_summary_amounts]` (Boolean, Default: False)
|
82
|
+
See [Caveats](#caveats) below. Optionally ignores the amounts in the account summary fields when calculating the account control checksum.
|
83
|
+
This value should be set only if you know that your bank uses this nonstandard calculation for
|
84
|
+
account control values.
|
85
|
+
|
86
|
+
* `options[:num_account_summary_continuation_records]` (Integer, Default: 0)
|
87
|
+
The number of continuation records the account summary.
|
88
|
+
|
89
|
+
|
90
|
+
##### Usage:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
Bai2::BaiFile.new(string_data,
|
94
|
+
account_control_ignores_summary_amounts: true,
|
95
|
+
num_account_summary_continuation_records: 3)
|
96
|
+
```
|
97
|
+
|
78
98
|
|
79
99
|
## Caveats
|
80
100
|
|
@@ -85,11 +105,13 @@ just an SVB quirk. I would love to hear how other banks do this. GitHub Issues
|
|
85
105
|
with more information on this would be greatly appreciated.
|
86
106
|
|
87
107
|
```ruby
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
108
|
+
# Some banks differ from the spec (*cough* SVB) and do not include
|
109
|
+
# the summary amounts in the control amount.
|
110
|
+
actual_sum = if options[:account_control_ignores_summary_amounts]
|
111
|
+
transaction_amounts_sum
|
112
|
+
else
|
113
|
+
transaction_amounts_sum + summary_amounts_sum
|
114
|
+
end
|
93
115
|
```
|
94
116
|
|
95
117
|
|
data/lib/bai2.rb
CHANGED
@@ -11,12 +11,17 @@ module Bai2
|
|
11
11
|
#
|
12
12
|
class BaiFile
|
13
13
|
|
14
|
+
DEFAULT_OPTIONS = {
|
15
|
+
account_control_ignores_summary_amounts: false,
|
16
|
+
num_account_summary_continuation_records: 0
|
17
|
+
}.freeze
|
18
|
+
|
14
19
|
# Parse a file on disk:
|
15
20
|
#
|
16
21
|
# f = BaiFile.parse('myfile.bai2')
|
17
22
|
#
|
18
|
-
def self.parse(path)
|
19
|
-
self.new(File.read(path))
|
23
|
+
def self.parse(path, options = {})
|
24
|
+
self.new(File.read(path), options)
|
20
25
|
end
|
21
26
|
|
22
27
|
|
@@ -24,9 +29,10 @@ module Bai2
|
|
24
29
|
#
|
25
30
|
# f = BaiFile.new(bai2_data)
|
26
31
|
#
|
27
|
-
def initialize(raw)
|
32
|
+
def initialize(raw, options = {})
|
28
33
|
@raw = raw
|
29
34
|
@groups = []
|
35
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
30
36
|
parse(raw)
|
31
37
|
end
|
32
38
|
|
data/lib/bai2/integrity.rb
CHANGED
@@ -36,12 +36,14 @@ module Bai2
|
|
36
36
|
end
|
37
37
|
|
38
38
|
# Run children assertions, which return number of records. May raise.
|
39
|
-
records = self.groups.map {|g| g.send(:assert_integrity
|
39
|
+
records = self.groups.map {|g| g.send(:assert_integrity!, @options) }.reduce(0, &:+)
|
40
40
|
|
41
|
-
unless expectation[:records] == (
|
41
|
+
unless expectation[:records] == (actual_num_records = records + 2)
|
42
42
|
raise IntegrityError.new(
|
43
|
-
"Record count invalid: file: #{expectation[:records]}, groups: #{
|
43
|
+
"Record count invalid: file: #{expectation[:records]}, groups: #{actual_num_records}")
|
44
44
|
end
|
45
|
+
|
46
|
+
actual_num_records
|
45
47
|
end
|
46
48
|
|
47
49
|
|
@@ -53,7 +55,7 @@ module Bai2
|
|
53
55
|
|
54
56
|
# Asserts integrity of a fully-parsed BaiFile by calculating checksums.
|
55
57
|
#
|
56
|
-
def assert_integrity!
|
58
|
+
def assert_integrity!(options)
|
57
59
|
expectation = {
|
58
60
|
sum: @trailer[:group_control_total],
|
59
61
|
children: @trailer[:number_of_accounts],
|
@@ -77,15 +79,15 @@ module Bai2
|
|
77
79
|
end
|
78
80
|
|
79
81
|
# Run children assertions, which return number of records. May raise.
|
80
|
-
records = self.accounts.map {|a| a.send(:assert_integrity
|
82
|
+
records = self.accounts.map {|a| a.send(:assert_integrity!, options) }.reduce(0, &:+)
|
81
83
|
|
82
|
-
unless expectation[:records] == (
|
84
|
+
unless expectation[:records] == (actual_num_records = records + 2)
|
83
85
|
raise IntegrityError.new(
|
84
|
-
"Record count invalid: group: #{expectation[:records]}, accounts: #{
|
86
|
+
"Record count invalid: group: #{expectation[:records]}, accounts: #{actual_num_records}")
|
85
87
|
end
|
86
88
|
|
87
89
|
# Return record count
|
88
|
-
|
90
|
+
actual_num_records
|
89
91
|
end
|
90
92
|
end
|
91
93
|
|
@@ -93,17 +95,23 @@ module Bai2
|
|
93
95
|
class Account
|
94
96
|
private
|
95
97
|
|
96
|
-
def assert_integrity!
|
98
|
+
def assert_integrity!(options)
|
97
99
|
expectation = {
|
98
100
|
sum: @trailer[:account_control_total],
|
99
101
|
records: @trailer[:number_of_records],
|
100
102
|
}
|
101
103
|
|
102
104
|
# Check sum vs. summary + transaction sums
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
105
|
+
summary_amounts_sum = self.summaries.map {|s| s[:amount] }.reduce(0, &:+)
|
106
|
+
transaction_amounts_sum = self.transactions.map(&:amount).reduce(0, &:+)
|
107
|
+
|
108
|
+
# Some banks differ from the spec (*cough* SVB) and do not include
|
109
|
+
# the summary amounts in the control amount.
|
110
|
+
actual_sum = if options[:account_control_ignores_summary_amounts]
|
111
|
+
transaction_amounts_sum
|
112
|
+
else
|
113
|
+
transaction_amounts_sum + summary_amounts_sum
|
114
|
+
end
|
107
115
|
|
108
116
|
unless expectation[:sum] == actual_sum
|
109
117
|
raise IntegrityError.new(
|
@@ -115,13 +123,19 @@ module Bai2
|
|
115
123
|
tx.instance_variable_get(:@record).physical_record_count
|
116
124
|
end.reduce(0, &:+)
|
117
125
|
|
118
|
-
|
119
|
-
|
120
|
-
|
126
|
+
# Account for the account header and the account trailer records
|
127
|
+
# and any additional summary records (Some banks use continuation records
|
128
|
+
# for account summaries, others put the summary data on the same row as the header)
|
129
|
+
additional_records = 2 + options[:num_account_summary_continuation_records]
|
130
|
+
actual_num_records = records + additional_records
|
131
|
+
|
132
|
+
unless expectation[:records] == actual_num_records
|
133
|
+
raise IntegrityError.new(
|
134
|
+
"Record count invalid: account: #{expectation[:records]}, transactions: #{actual_num_records}")
|
121
135
|
end
|
122
136
|
|
123
137
|
# Return record count
|
124
|
-
|
138
|
+
actual_num_records
|
125
139
|
end
|
126
140
|
end
|
127
141
|
end
|
data/lib/bai2/record.rb
CHANGED
data/lib/bai2/version.rb
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
01,121140399,9999999999,050608,1015,1,80,1,2/
|
2
|
+
02,9999999999,121140399,1,050607,1015,,2/
|
3
|
+
03,1234567890,USD,010,000,,,015,000,,/
|
4
|
+
88,040,000,,,045,000,,/
|
5
|
+
88,072,000,,,074,000,,/
|
6
|
+
88,100,25001,17,,400,000,0,/
|
7
|
+
16,174,25001,Z,,50848,/
|
8
|
+
88,SAMPLE CONTINUATION TEXT,/
|
9
|
+
49,50002,7/
|
10
|
+
98,50002,1,9/
|
11
|
+
99,50002,1,11/
|
@@ -0,0 +1,17 @@
|
|
1
|
+
01,121140399,3333333333,100831,1720,000001,80,1,2/
|
2
|
+
02,3333333333,121140399,1,100831,,USD,4/
|
3
|
+
03,3333333333,USD,,,,/
|
4
|
+
16,195,8325982,,,,FED NO: 20100831L1B77D1CDSDSDJSIO15608310954FT01
|
5
|
+
88,SENDER BNK:=ETRADE BANK
|
6
|
+
88,SENDER ID:=056073573
|
7
|
+
88,ORG:=OPTIONS LINK WIRE CLEARING
|
8
|
+
88,ORG ADDRESS:=1995 SE. 57TH ST. NY, NY 10022
|
9
|
+
88,BNF ID:=3300333333
|
10
|
+
88,BNF NAME:=YOUR NAME HERE INC
|
11
|
+
88,BNF ADDRESS:=185 B ST SAN FRAN, CA 94011
|
12
|
+
88,REC FI:=SIL VLY BK SCLA
|
13
|
+
88,REC ID:=121140399
|
14
|
+
88,OBI:=INVOICE 123456
|
15
|
+
49,8325982,13/
|
16
|
+
98,8325982,1,15/
|
17
|
+
99,8325982,1,17/
|
@@ -0,0 +1,17 @@
|
|
1
|
+
01,121140399,3333333333,100831,1720,000001,80,1,2/
|
2
|
+
02,3333333333,121140399,1,100831,1720,USD,4/
|
3
|
+
03,3333333333,USD,,,,/
|
4
|
+
16,195,8325982,,,,FED NO: 20100831L1B77D1CDSDSDJSIO15608310954FT01
|
5
|
+
88,SENDER BNK:=ETRADE BANK
|
6
|
+
88,SENDER ID:=056073573
|
7
|
+
88,ORG:=OPTIONS LINK WIRE CLEARING
|
8
|
+
88,ORG ADDRESS:=1995 SE. 57TH ST. NY, NY 10022
|
9
|
+
88,BNF ID:=3300333333
|
10
|
+
88,BNF NAME:=YOUR NAME HERE INC
|
11
|
+
88,BNF ADDRESS:=185 B ST SAN FRAN, CA 94011
|
12
|
+
88,REC FI:=SIL VLY BK SCLA
|
13
|
+
88,REC ID:=121140399
|
14
|
+
88,OBI:=INVOICE 123456
|
15
|
+
49,8325983,13/
|
16
|
+
98,8325983,1,15/
|
17
|
+
99,8325983,1,17/
|
data/test/tests/bai2.rb
CHANGED
@@ -6,16 +6,23 @@ class Bai2Test < Minitest::Test
|
|
6
6
|
|
7
7
|
def setup
|
8
8
|
@daily = Bai2::BaiFile.parse(File.expand_path('../../data/daily.bai2', __FILE__))
|
9
|
+
@daily_with_summary = Bai2::BaiFile.parse(File.expand_path('../../data/daily_with_summary.bai2', __FILE__),
|
10
|
+
num_account_summary_continuation_records: 3)
|
11
|
+
|
9
12
|
@eod = Bai2::BaiFile.parse(File.expand_path('../../data/eod.bai2', __FILE__))
|
13
|
+
@eod_no_as_of_time = Bai2::BaiFile.parse(File.expand_path('../../data/eod_without_as_of_time.bai2', __FILE__))
|
14
|
+
|
15
|
+
@all_files = [@daily, @daily_with_summary, @eod, @eod_no_as_of_time]
|
10
16
|
end
|
11
17
|
|
12
18
|
def test_parsing
|
13
|
-
|
14
|
-
|
19
|
+
@all_files.each do |file|
|
20
|
+
assert_kind_of(Bai2::BaiFile, file)
|
21
|
+
end
|
15
22
|
end
|
16
23
|
|
17
24
|
def test_groups
|
18
|
-
|
25
|
+
@all_files.each do |file|
|
19
26
|
assert_kind_of(Array, file.groups)
|
20
27
|
assert_equal(1, file.groups.count)
|
21
28
|
group = file.groups.first
|
@@ -27,8 +34,8 @@ class Bai2Test < Minitest::Test
|
|
27
34
|
end
|
28
35
|
|
29
36
|
def test_accounts
|
30
|
-
|
31
|
-
accounts =
|
37
|
+
@all_files.each do |file|
|
38
|
+
accounts = file.groups.first.accounts
|
32
39
|
assert_kind_of(Array, accounts)
|
33
40
|
assert_equal(1, accounts.count)
|
34
41
|
assert_kind_of(Bai2::BaiFile::Account, accounts.first)
|
@@ -56,4 +63,15 @@ class Bai2Test < Minitest::Test
|
|
56
63
|
})
|
57
64
|
end
|
58
65
|
|
66
|
+
def test_integrity
|
67
|
+
assert_raises Bai2::BaiFile::IntegrityError do
|
68
|
+
# Calling without the options: num_account_summary_continuation_records => 3 should raise an error
|
69
|
+
Bai2::BaiFile.parse(File.expand_path('../../data/daily_with_summary.bai2', __FILE__))
|
70
|
+
end
|
71
|
+
assert_raises Bai2::BaiFile::IntegrityError do
|
72
|
+
# An invalid amount checksum should raise an error
|
73
|
+
Bai2::BaiFile.parse(File.expand_path('../../data/invalid_checksum_eod.bai2', __FILE__))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
59
77
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bai2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenneth Ballenegger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -74,6 +74,7 @@ extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
76
|
- ".gitignore"
|
77
|
+
- CHANGELOG.md
|
77
78
|
- Gemfile
|
78
79
|
- LICENSE.txt
|
79
80
|
- README.md
|
@@ -88,7 +89,10 @@ files:
|
|
88
89
|
- lib/bai2/version.rb
|
89
90
|
- test/autorun.rb
|
90
91
|
- test/data/daily.bai2
|
92
|
+
- test/data/daily_with_summary.bai2
|
91
93
|
- test/data/eod.bai2
|
94
|
+
- test/data/eod_without_as_of_time.bai2
|
95
|
+
- test/data/invalid_checksum_eod.bai2
|
92
96
|
- test/tests/bai2.rb
|
93
97
|
- test/tests/parsing.rb
|
94
98
|
homepage: https://github.com/venturehacks/bai2
|
@@ -111,14 +115,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
115
|
version: '0'
|
112
116
|
requirements: []
|
113
117
|
rubyforge_project:
|
114
|
-
rubygems_version: 2.2.
|
118
|
+
rubygems_version: 2.5.2.3
|
115
119
|
signing_key:
|
116
120
|
specification_version: 4
|
117
121
|
summary: Parse BAI2 files.
|
118
122
|
test_files:
|
119
123
|
- test/autorun.rb
|
120
124
|
- test/data/daily.bai2
|
125
|
+
- test/data/daily_with_summary.bai2
|
121
126
|
- test/data/eod.bai2
|
127
|
+
- test/data/eod_without_as_of_time.bai2
|
128
|
+
- test/data/invalid_checksum_eod.bai2
|
122
129
|
- test/tests/bai2.rb
|
123
130
|
- test/tests/parsing.rb
|
124
|
-
has_rdoc:
|