bai2 1.0.0 → 2.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 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: