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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b83aa7ebed34d3059973ec3d76e15f1e32d05814
4
- data.tar.gz: fe4bec1ec7552da50e366d60113819c817dd92af
3
+ metadata.gz: 975f734808bc96fce7ca38c08e1ba90e8839b8c6
4
+ data.tar.gz: 99d17eec7002b0b0c6ca70b9a3228f2500efadc5
5
5
  SHA512:
6
- metadata.gz: 1c318047c59369b2f709189e7efcbde12cc323368da2614e1b9d49d462be44f8a2f490e0b80c700aadb909001449640b0a1eaa4de09cd70d2b860d3f3fb2407e
7
- data.tar.gz: 0eab9ce6627092f68b1749f2e2202f472706543c6728c578c8d8f6e490f2f06e759dff37ba7b3001e54059878ac53e68cd63a91b0532ba2a19b1e5eeb8e138ad
6
+ metadata.gz: bdd5385fe33114722442f33838c2bab6406cfc41ab7a1e3125bd09bf1d269e351e57008090da8ccb34b6ae11edefd4ec222cf9ae7fad32edcd3fb86a9b18bb6e
7
+ data.tar.gz: eb2228fdfaaba4856d141508995809e7ecc0235de2b6437206faa774c9382c469486ae9a001be4a2398746882a697b59936fd01aad5cf1713382159c6eed3ed7
@@ -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
- # Check sum vs. summary + transaction sums
89
- actual_sum = self.transactions.map(&:amount).reduce(0, &:+) \
90
- #+ self.summaries.map {|s| s[:amount] }.reduce(0, &:+)
91
- # TODO: ^ there seems to be a disconnect between what the spec defines
92
- # as the formula for the checksum and what SVB implements...
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
 
@@ -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
 
@@ -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!) }.reduce(0, &:+)
39
+ records = self.groups.map {|g| g.send(:assert_integrity!, @options) }.reduce(0, &:+)
40
40
 
41
- unless expectation[:records] == (actual = records + 2)
41
+ unless expectation[:records] == (actual_num_records = records + 2)
42
42
  raise IntegrityError.new(
43
- "Record count invalid: file: #{expectation[:records]}, groups: #{actual}")
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!) }.reduce(0, &:+)
82
+ records = self.accounts.map {|a| a.send(:assert_integrity!, options) }.reduce(0, &:+)
81
83
 
82
- unless expectation[:records] == (actual = records + 2)
84
+ unless expectation[:records] == (actual_num_records = records + 2)
83
85
  raise IntegrityError.new(
84
- "Record count invalid: group: #{expectation[:records]}, accounts: #{actual}")
86
+ "Record count invalid: group: #{expectation[:records]}, accounts: #{actual_num_records}")
85
87
  end
86
88
 
87
89
  # Return record count
88
- records + 2
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
- actual_sum = self.transactions.map(&:amount).reduce(0, &:+) \
104
- #+ self.summaries.map {|s| s[:amount] }.reduce(0, &:+)
105
- # TODO: ^ there seems to be a disconnect between what the spec defines
106
- # as the formula for the checksum and what SVB implements...
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
- unless expectation[:records] == (actual = records + 2)
119
- raise IntegrityError.new("Record count invalid: " \
120
- + "account: #{expectation[:records]}, transactions: #{actual}")
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
- records + 2
138
+ actual_num_records
125
139
  end
126
140
  end
127
141
  end
@@ -30,7 +30,7 @@ module Bai2
30
30
 
31
31
  # Returns a time interval in seconds, to be added to the date
32
32
  ParseMilitaryTime = -> (v) do
33
- v = '2400' if v == '9999'
33
+ v = '2400' if (v == '' || v == '9999')
34
34
  Time.strptime("#{v} utc", '%H%M %Z').to_i % 86400
35
35
  end
36
36
 
@@ -1,3 +1,3 @@
1
1
  module Bai2
2
- VERSION = '1.0.0'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -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/
@@ -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
- assert_kind_of(Bai2::BaiFile, @daily)
14
- assert_kind_of(Bai2::BaiFile, @eod)
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
- [@daily, @eod].each do |file|
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
- [@daily, @eod].each do |file|
31
- accounts = @daily.groups.first.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: 1.0.0
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: 2015-03-20 00:00:00.000000000 Z
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.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: