headache 0.1.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/.gitignore +10 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +105 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/headache.gemspec +26 -0
- data/lib/headache/batch.rb +84 -0
- data/lib/headache/definition/batch_control.rb +25 -0
- data/lib/headache/definition/batch_header.rb +28 -0
- data/lib/headache/definition/common.rb +31 -0
- data/lib/headache/definition/entry.rb +33 -0
- data/lib/headache/definition/file_control.rb +21 -0
- data/lib/headache/definition/file_header.rb +29 -0
- data/lib/headache/definitions.rb +6 -0
- data/lib/headache/document.rb +80 -0
- data/lib/headache/formatters.rb +30 -0
- data/lib/headache/record/batch_control.rb +22 -0
- data/lib/headache/record/batch_header.rb +20 -0
- data/lib/headache/record/entry.rb +16 -0
- data/lib/headache/record/file_control.rb +46 -0
- data/lib/headache/record/file_header.rb +32 -0
- data/lib/headache/record/overflow.rb +13 -0
- data/lib/headache/records.rb +6 -0
- data/lib/headache/version.rb +3 -0
- data/lib/headache.rb +11 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 666d14df0f9f60429e47c14b581ad73fd37611e7
|
4
|
+
data.tar.gz: e75fd53aa8b9c3e8708ad252e9adc83722aefd66
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a79fdbed3a9c657c79137485d88315209af552f135dab39f2e50ab366aa996cae2d53de1062c5fa5181a5af668c633cb17d2b6641030b20b4bc8c3cbc7c9c884
|
7
|
+
data.tar.gz: cac050138755d8ca8575441db4736c8a2aa3a60b879af9aa90617e131a3b087cc8fb73275c1c31fce08f770b638fc0237e0c1823360f7f138461bfe680f6427b
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# HeadACHe
|
2
|
+
|
3
|
+
Headache takes a lot of the guesswork out of building [ACH (Automated Clearing House)](https://en.wikipedia.org/wiki/Automated_Clearing_House) files to move money around between banks.
|
4
|
+
|
5
|
+
Headache is built on top of the excellent [Fixy gem](https://github.com/Chetane/fixy) for handling fixed-width files.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'headache'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install headache
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
It is outside of the scope of this document to describe NACHA's ACH file format. There are plenty of resources available that explain the syntax in detail.
|
26
|
+
|
27
|
+
### Record Types
|
28
|
+
|
29
|
+
ACH files contain 5 different record types. They are:
|
30
|
+
|
31
|
+
* File Header `Headache::Record::FileHeader`
|
32
|
+
* Batch Header `Headache::Record::FileHeader`
|
33
|
+
* Entry Detail `Headache::Record::Entry`
|
34
|
+
* Batch Control `Headache::Record::BatchControl`
|
35
|
+
* File Control `Headache::Record::FileControl`
|
36
|
+
|
37
|
+
A batch itself is represented by `Headache::Batch` and the document is unsurprisingly `Headache::Document`.
|
38
|
+
|
39
|
+
### Building an ACH Document
|
40
|
+
|
41
|
+
Headache provides some convenience out-of-the-box if you only plan to generate ACH files that contain a single batch, however there's nothing stopping you from implementing multiple batches.
|
42
|
+
|
43
|
+
Calling `#batch` or `#first_batch` will automatically create a `Headache::Batch` if the document's batch list is empty. `#batch` will raise an exception if the document contains multiple batches.
|
44
|
+
|
45
|
+
All ACH fields are represented and available to you. Headache attempts to abstract batch header and control records, allowing you to specify those details directly on the `Batch` object itself, but more granular control over that data is available if needed.
|
46
|
+
|
47
|
+
Further, Headache abstracts an ACH record _definition_ from the record itself; so should you need to create your own record and document objects, you're free to re-use the [definition mixins](https://github.com/teampayoff/headache/blob/master/lib/headache/definition) for this purpose.
|
48
|
+
|
49
|
+
Create a new ACH document (some optional fields are omitted in this example):
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
document = Headache::Document.new.tap do |document|
|
53
|
+
document.header.tap do |file_header|
|
54
|
+
file_header.destination_name = '1ST INTERNET BANK'
|
55
|
+
file_header.destination = '111111111' # Originating DFI number
|
56
|
+
file_header.origin_name = 'ACME CORP'
|
57
|
+
file_header.origin = '1111111111'
|
58
|
+
file_header.reference_code = '11111111'
|
59
|
+
end
|
60
|
+
document.batch.tap do |batch|
|
61
|
+
batch.type = :debit
|
62
|
+
batch.odfi_id = '11111111'
|
63
|
+
batch.company_identification = '1111111111'
|
64
|
+
batch.batch_number = 1
|
65
|
+
batch.effective_date = Date.today
|
66
|
+
batch.company_name = 'ACME CORP FTW'
|
67
|
+
batch.entry_description = 'My Description'
|
68
|
+
batch.entry_class_code = 'WEB'
|
69
|
+
batch.discretionary = 'My Discretionary Data'
|
70
|
+
|
71
|
+
# Now, for each credit/debit, generate an Entry ...
|
72
|
+
batch << Headache::Record::Entry.new.tap do |entry|
|
73
|
+
entry.routing_number = '123456789'
|
74
|
+
entry.account_number = '1234567890'
|
75
|
+
entry.amount = -100_00 # $100.00 (negative, since we're debiting)
|
76
|
+
entry.individual_name = 'Bob Smith'
|
77
|
+
|
78
|
+
# these fields are for your own tracking...
|
79
|
+
entry.trace_number = '1234567890'
|
80
|
+
entry.internal_id = '1234567890'
|
81
|
+
entry.transaction_code = '1234567890'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
Finally, you can generate the file:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
document.generate_to_file 'ACH.txt'
|
91
|
+
```
|
92
|
+
|
93
|
+
## Development
|
94
|
+
|
95
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
96
|
+
|
97
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
98
|
+
|
99
|
+
## Contributing
|
100
|
+
|
101
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/teampayoff/headache.
|
102
|
+
|
103
|
+
## Authors
|
104
|
+
|
105
|
+
Made possible by all of our [contributors](https://github.com/teampayoff/headache/graphs/contributors).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "headache"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/headache.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'headache/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'headache'
|
8
|
+
spec.version = Headache::VERSION
|
9
|
+
spec.authors = ['Payoff, Inc.', 'Chris Bielinski']
|
10
|
+
spec.email = ['engineering@payoff.com', 'cbielinski@payoff.com']
|
11
|
+
spec.licenses = ['MIT']
|
12
|
+
spec.summary = 'Take the pain out of building ACH files.'
|
13
|
+
spec.description = 'Take the pain out of building ACH files. Wraps the Fixy gem for building fixed-with ACH files as specified by NACHA.'
|
14
|
+
spec.homepage = 'https://github.com/teampayoff/headache'
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.bindir = 'exe'
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
spec.add_dependency 'fixy', '~> 0.0'
|
20
|
+
spec.add_dependency 'activesupport', '~> 4'
|
21
|
+
spec.add_development_dependency 'faker', '~> 1.4'
|
22
|
+
spec.add_development_dependency 'rspec', '~> 3.3'
|
23
|
+
spec.add_development_dependency 'factory_girl', '~> 4.5'
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.10'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Headache
|
2
|
+
class Batch
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_accessor :type, :document, :batch_number, :service_code, :odfi_id,
|
6
|
+
:company_name, :company_identification, :effective_date,
|
7
|
+
:company_name, :company_identification, :discretionary,
|
8
|
+
:entry_class_code, :entry_description
|
9
|
+
|
10
|
+
@@record_classes = { header: Record::BatchHeader,
|
11
|
+
control: Record::BatchControl }
|
12
|
+
|
13
|
+
def self.header=(klass)
|
14
|
+
set_class :header, klass
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.control=(klass)
|
18
|
+
set_class :control, klass
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.set_class(type, klass)
|
22
|
+
fail "unknown record class: #{type}" unless @@record_classes[type].present?
|
23
|
+
@@record_classes[type] = klass
|
24
|
+
end
|
25
|
+
|
26
|
+
def effective_date
|
27
|
+
@effective_date || Date.today
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(document = nil)
|
31
|
+
@document = document
|
32
|
+
@members = []
|
33
|
+
end
|
34
|
+
|
35
|
+
def service_code
|
36
|
+
return '220' if type == :credit
|
37
|
+
return '225' if type == :debit
|
38
|
+
fail "unknown batch type: #{type.inspect} (expecting :credit or :debit)"
|
39
|
+
end
|
40
|
+
|
41
|
+
def header
|
42
|
+
@header ||= @@record_classes[:header].new self, @document
|
43
|
+
end
|
44
|
+
|
45
|
+
def control
|
46
|
+
@control ||= @@record_classes[:control].new self, @document
|
47
|
+
end
|
48
|
+
|
49
|
+
def entries
|
50
|
+
@members
|
51
|
+
end
|
52
|
+
|
53
|
+
def batch_number
|
54
|
+
@batch_number || document.batches.index(batch) + 1
|
55
|
+
end
|
56
|
+
|
57
|
+
def entry_hash
|
58
|
+
entries.map(&:routing_identification).map(&:to_i).sum
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_entry(entry)
|
62
|
+
entry.batch = self
|
63
|
+
@members << entry
|
64
|
+
end
|
65
|
+
|
66
|
+
def <<(entry_or_entries)
|
67
|
+
(entries.is_a?(Array) ? entry_or_entries : [entry_or_entries]).each { |e| add_entry e }
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def method_missing(m, *args, &block)
|
72
|
+
entries.send m, *args, &block
|
73
|
+
end
|
74
|
+
|
75
|
+
def total_debit
|
76
|
+
entries.map(&:amount).select { |amt| amt < 0 }.map(&:abs).sum
|
77
|
+
end
|
78
|
+
|
79
|
+
def total_credit
|
80
|
+
entries.map(&:amount).select { |amt| amt > 0 }.sum
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Headache
|
2
|
+
module Definition
|
3
|
+
module BatchControl
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include Common
|
8
|
+
|
9
|
+
field :service_code, 3, '2-4', :alphanumeric # Field2
|
10
|
+
field :entry_count, 6, '5-10', :numeric # Field3
|
11
|
+
field :entry_hash, 10, '11-20', :numeric # Field4
|
12
|
+
field :total_debit, 12, '21-32', :numeric # Field5
|
13
|
+
field :total_credit, 12, '33-44', :numeric # Field6
|
14
|
+
field :company_identification, 10, '45-54', :alphanumeric # Field7
|
15
|
+
field :message_authentication_code, 19, '55-73', :nothing # Field8
|
16
|
+
field :reserved, 6, '74-79', :nothing # Field9
|
17
|
+
field :odfi_id, 8, '80-87', :alphanumeric # Field10
|
18
|
+
field :batch_number, 7, '88-94', :numeric # Field11
|
19
|
+
|
20
|
+
field_value :message_authentication_code, ''
|
21
|
+
field_value :reserved, ''
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Headache
|
2
|
+
module Definition
|
3
|
+
module BatchHeader
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include Common
|
8
|
+
|
9
|
+
field :service_code, 3, '2-4', :alphanumeric # Field2 credits = 220, debits = 225
|
10
|
+
field :company_name, 16, '5-20', :alphanumeric # Field3
|
11
|
+
field :discretionary, 20, '21-40', :alphanumeric # Field4
|
12
|
+
field :company_identification, 10, '41-50', :alphanumeric # Field5
|
13
|
+
field :entry_class_code, 3, '51-53', :alphanumeric # Field6
|
14
|
+
field :entry_description, 10, '54-63', :alphanumeric # Field7
|
15
|
+
field :descriptive_date, 6, '64-69', :nothing # Field8
|
16
|
+
field :effective_date, 6, '70-75', :date # Field9
|
17
|
+
field :settlement_date, 3, '76-78', :nothing # Field10
|
18
|
+
field :originator_status_code, 1, '79-79', :alphanumeric # Field11
|
19
|
+
field :odfi_id, 8, '80-87', :alphanumeric # Field12
|
20
|
+
field :batch_number, 7, '88-94', :alphanumeric # Field13
|
21
|
+
|
22
|
+
field_value :descriptive_date, ''
|
23
|
+
field_value :settlement_date, ''
|
24
|
+
field_value :originator_status_code, '1'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Headache
|
2
|
+
module Definition
|
3
|
+
module Common
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
attr_accessor :document
|
8
|
+
|
9
|
+
set_record_length 94
|
10
|
+
set_line_ending Fixy::Record::LINE_ENDING_CRLF
|
11
|
+
|
12
|
+
include Formatters
|
13
|
+
|
14
|
+
field :record_type_code, 1, '1-1', :alphanumeric
|
15
|
+
|
16
|
+
def record_type_codes
|
17
|
+
{ file_header: 1,
|
18
|
+
batch_header: 5,
|
19
|
+
entry: 6,
|
20
|
+
batch_control: 8,
|
21
|
+
file_control: 9 }
|
22
|
+
end
|
23
|
+
|
24
|
+
def record_type_code
|
25
|
+
record_type = self.class.name.demodulize.underscore
|
26
|
+
record_type_codes[record_type.to_sym]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Headache
|
2
|
+
module Definition
|
3
|
+
module Entry
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include Common
|
8
|
+
|
9
|
+
field :transaction_code, 2, '2-3', :alphanumeric # Field2
|
10
|
+
field :routing_identification, 8, '4-11', :numeric # Field3
|
11
|
+
field :check_digit, 1, '12-12', :numeric # Field4
|
12
|
+
field :account_number, 17, '13-29', :alphanumeric # Field5
|
13
|
+
field :amount, 10, '30-39', :numeric # Field6
|
14
|
+
field :internal_id, 15, '40-54', :alphanumeric # Field7
|
15
|
+
field :individual_name, 22, '55-76', :alphanumeric # Field8
|
16
|
+
field :discretionary, 2, '77-78', :alphanumeric # Field9
|
17
|
+
field :addenda_record, 1, '79-79', :alphanumeric # Field10
|
18
|
+
field :trace_number, 15, '80-94', :alphanumeric # Field11
|
19
|
+
|
20
|
+
field_value :addenda_record, '0'
|
21
|
+
field_value :discretionary, ''
|
22
|
+
end
|
23
|
+
|
24
|
+
def routing_identification
|
25
|
+
routing_number.first 8
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_digit
|
29
|
+
routing_number.last 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Headache
|
2
|
+
module Definition
|
3
|
+
module FileControl
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include Common
|
8
|
+
|
9
|
+
field :batch_count, 6, '2-7', :numeric
|
10
|
+
field :block_count, 6, '8-13', :numeric
|
11
|
+
field :entry_count, 8, '14-21', :numeric
|
12
|
+
field :entry_hash, 10, '22-31', :numeric
|
13
|
+
field :total_debit, 12, '32-43', :numeric
|
14
|
+
field :total_credit, 12, '44-55', :numeric
|
15
|
+
field :reserved, 39, '56-94', :nothing
|
16
|
+
|
17
|
+
field_value :reserved, ''
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Headache
|
2
|
+
module Definition
|
3
|
+
module FileHeader
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include Common
|
8
|
+
|
9
|
+
field :priority_code, 2, '2-3', :numeric # Field2
|
10
|
+
field :destination, 10, '4-13', :alphanumeric # Field3
|
11
|
+
field :origin, 10, '14-23', :alphanumeric # Field4
|
12
|
+
field :creation_date, 6, '24-29', :date # Field5
|
13
|
+
field :creation_time, 4, '30-33', :time # Field6
|
14
|
+
field :id_modifier, 1, '34-34', :alphanumeric # Field7
|
15
|
+
field :record_size, 3, '35-37', :alphanumeric # Field8
|
16
|
+
field :blocking_factor, 2, '38-39', :alphanumeric # Field9
|
17
|
+
field :format_code, 1, '40-40', :alphanumeric # Field10
|
18
|
+
field :destination_name, 23, '41-63', :alphanumeric # Field11
|
19
|
+
field :origin_name, 23, '64-86', :alphanumeric # Field12
|
20
|
+
field :reference_code, 8, '87-94', :alphanumeric # Field13
|
21
|
+
|
22
|
+
field_value :priority_code, '01'
|
23
|
+
field_value :record_size, '094'
|
24
|
+
field_value :blocking_factor, '10'
|
25
|
+
field_value :format_code, '1'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Headache
|
2
|
+
class Document < Fixy::Document
|
3
|
+
attr_reader :batches
|
4
|
+
|
5
|
+
delegate :add_entry, :'<<', to: :first_batch
|
6
|
+
|
7
|
+
@@record_classes = { batch: Batch,
|
8
|
+
header: Record::FileHeader,
|
9
|
+
control: Record::FileControl }
|
10
|
+
|
11
|
+
def self.header=(klass)
|
12
|
+
set_class :header, klass
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.control=(klass)
|
16
|
+
set_class :control, klass
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.set_class(type, klass)
|
20
|
+
fail "unknown record class: #{type}" unless @@record_classes[type].present?
|
21
|
+
@@record_classes[type] = klass
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@batches = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def first_batch
|
29
|
+
add_batch @@record_classes[:batch].new(self) if @batches.empty?
|
30
|
+
@batches.first
|
31
|
+
end
|
32
|
+
|
33
|
+
def batch
|
34
|
+
fail 'multiple batches detected, be more explicit or use first_batch' if @batches.count > 1
|
35
|
+
first_batch
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_batch(batch)
|
39
|
+
batch.document = self
|
40
|
+
@batches << batch # @@record_classes[:batch].new(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def <<(batch)
|
44
|
+
add_batch batch
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def entries
|
49
|
+
@batches.map(&:entries).flatten
|
50
|
+
end
|
51
|
+
|
52
|
+
def header
|
53
|
+
@header ||= @@record_classes[:header].new self
|
54
|
+
end
|
55
|
+
|
56
|
+
def control
|
57
|
+
@control ||= @@record_classes[:control].new self
|
58
|
+
end
|
59
|
+
|
60
|
+
def lines
|
61
|
+
@content.split("\n").count
|
62
|
+
end
|
63
|
+
|
64
|
+
def overflow_lines_needed
|
65
|
+
10 - lines % 10
|
66
|
+
end
|
67
|
+
|
68
|
+
def build
|
69
|
+
append_record header
|
70
|
+
@batches.each do |batch|
|
71
|
+
append_record batch.header
|
72
|
+
batch.entries.each { |entry| append_record entry }
|
73
|
+
append_record batch.control
|
74
|
+
end
|
75
|
+
append_record control
|
76
|
+
overflow_lines_needed.times { append_record Record::Overflow.new }
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Headache
|
2
|
+
module Formatters
|
3
|
+
include Fixy::Formatter::Alphanumeric
|
4
|
+
|
5
|
+
def format_alphanumeric(input, length)
|
6
|
+
super(input, length).upcase
|
7
|
+
end
|
8
|
+
|
9
|
+
def format_date(input, _length)
|
10
|
+
fail "input #{input.inspect} does not respond to #strftime!" unless input.respond_to?(:strftime)
|
11
|
+
input.strftime '%y%m%d'
|
12
|
+
end
|
13
|
+
|
14
|
+
def format_nothing(_input, length)
|
15
|
+
' ' * length
|
16
|
+
end
|
17
|
+
|
18
|
+
def format_numeric(input, length)
|
19
|
+
input = input.to_i.abs.to_s
|
20
|
+
fail ArgumentError, "Invalid Input: #{input.inspect} (only digits are accepted) from #{self.class.to_s.demodulize}" unless input =~ /^\d+$/
|
21
|
+
fail ArgumentError, "Not enough length (input: #{input}, length: #{length})" if input.length > length
|
22
|
+
input.rjust(length, '0')
|
23
|
+
end
|
24
|
+
|
25
|
+
def format_time(input, _length)
|
26
|
+
fail "input #{input.inspect} does not respond to #strftime!" unless input.respond_to?(:strftime)
|
27
|
+
input.strftime '%H%M'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Headache
|
2
|
+
module Record
|
3
|
+
class BatchControl < Fixy::Record
|
4
|
+
include Definition::BatchControl
|
5
|
+
|
6
|
+
attr_accessor :batch, :document
|
7
|
+
|
8
|
+
delegate :entry_hash, :total_debit, :total_credit, :batch_number,
|
9
|
+
:service_code, :odfi_id, :company_identification, to: :batch
|
10
|
+
|
11
|
+
def initialize(batch, document)
|
12
|
+
@batch = batch
|
13
|
+
@document = document
|
14
|
+
end
|
15
|
+
|
16
|
+
def entry_count
|
17
|
+
batch.size
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Headache
|
2
|
+
module Record
|
3
|
+
class BatchHeader < Fixy::Record
|
4
|
+
include Definition::BatchHeader
|
5
|
+
|
6
|
+
attr_accessor :batch, :document
|
7
|
+
|
8
|
+
delegate :batch_number, :'batch_number=', :service_code, :odfi_id, :'odfi_id=',
|
9
|
+
:company_identification, :'company_identification=', :company_name,
|
10
|
+
:'company_name=', :entry_description, :'entry_description=', :discretionary,
|
11
|
+
:'discretionary=', :entry_class_code, :'entry_class_code=', :effective_date,
|
12
|
+
:'effective_date=', to: :batch
|
13
|
+
|
14
|
+
def initialize(batch = nil, document = nil)
|
15
|
+
@batch = batch
|
16
|
+
@document = document
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Headache
|
2
|
+
module Record
|
3
|
+
class Entry < Fixy::Record
|
4
|
+
include Definition::Entry
|
5
|
+
|
6
|
+
attr_accessor :routing_number, :account_number, :amount,
|
7
|
+
:internal_id, :trace_number, :individual_name,
|
8
|
+
:transaction_code, :document, :batch, :discretionary
|
9
|
+
|
10
|
+
def initialize(batch = nil, document = nil)
|
11
|
+
@batch = batch
|
12
|
+
@document = document
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Headache
|
2
|
+
module Record
|
3
|
+
class FileControl < Fixy::Record
|
4
|
+
include Definition::FileControl
|
5
|
+
|
6
|
+
attr_accessor :document
|
7
|
+
|
8
|
+
def initialize(document = nil)
|
9
|
+
@document = document
|
10
|
+
end
|
11
|
+
|
12
|
+
def batch_count
|
13
|
+
document.batches.count
|
14
|
+
end
|
15
|
+
|
16
|
+
def block_count
|
17
|
+
(((batch_count * 2) + document.entries.count + 2) / 10.0).ceil
|
18
|
+
end
|
19
|
+
|
20
|
+
def entry_count
|
21
|
+
document.entries.count
|
22
|
+
end
|
23
|
+
|
24
|
+
def entry_hash
|
25
|
+
batch_sum(:entry_hash).to_s.last(10)
|
26
|
+
end
|
27
|
+
|
28
|
+
def total_debit
|
29
|
+
batch_sum :total_debit
|
30
|
+
end
|
31
|
+
|
32
|
+
def total_credit
|
33
|
+
batch_sum :total_credit
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def batch_sum(method)
|
39
|
+
document.batches
|
40
|
+
.map { |b| b.send method }
|
41
|
+
.map(&:to_i).sum
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Headache
|
2
|
+
module Record
|
3
|
+
class FileHeader < Fixy::Record
|
4
|
+
include Definition::FileHeader
|
5
|
+
|
6
|
+
attr_accessor :reference_code, :creation_time, :origin, :origin_name,
|
7
|
+
:destination, :destination_name, :document, :creation_date,
|
8
|
+
:id_modifier
|
9
|
+
|
10
|
+
def initialize(document = nil)
|
11
|
+
@document = document
|
12
|
+
end
|
13
|
+
|
14
|
+
def destination
|
15
|
+
' ' + (@destination || '')
|
16
|
+
end
|
17
|
+
|
18
|
+
def creation_date
|
19
|
+
@creation_date || Date.today
|
20
|
+
end
|
21
|
+
|
22
|
+
def creation_time
|
23
|
+
@creation_time || Time.now
|
24
|
+
end
|
25
|
+
|
26
|
+
def id_modifier
|
27
|
+
@id_modifier || 'A'
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Headache
|
2
|
+
module Record
|
3
|
+
class Overflow < Fixy::Record
|
4
|
+
include Formatters
|
5
|
+
|
6
|
+
set_record_length 94
|
7
|
+
set_line_ending Fixy::Record::LINE_ENDING_CRLF
|
8
|
+
|
9
|
+
field :overflow, 94, '1-94', :numeric
|
10
|
+
field_value :overflow, -> { '9' * 94 }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/headache.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'fixy'
|
2
|
+
|
3
|
+
require 'active_support/concern'
|
4
|
+
require 'active_support/core_ext/string'
|
5
|
+
require 'active_support/core_ext/enumerable'
|
6
|
+
|
7
|
+
require 'headache/formatters'
|
8
|
+
require 'headache/definitions'
|
9
|
+
require 'headache/records'
|
10
|
+
require 'headache/batch'
|
11
|
+
require 'headache/document'
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: headache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Payoff, Inc.
|
8
|
+
- Chris Bielinski
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-08-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fixy
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0.0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0.0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: activesupport
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '4'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '4'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: faker
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.4'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.4'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rspec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '3.3'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.3'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: factory_girl
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '4.5'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '4.5'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: bundler
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '1.10'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '1.10'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: rake
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '10.0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '10.0'
|
112
|
+
description: Take the pain out of building ACH files. Wraps the Fixy gem for building
|
113
|
+
fixed-with ACH files as specified by NACHA.
|
114
|
+
email:
|
115
|
+
- engineering@payoff.com
|
116
|
+
- cbielinski@payoff.com
|
117
|
+
executables: []
|
118
|
+
extensions: []
|
119
|
+
extra_rdoc_files: []
|
120
|
+
files:
|
121
|
+
- ".gitignore"
|
122
|
+
- ".travis.yml"
|
123
|
+
- Gemfile
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- bin/console
|
127
|
+
- bin/setup
|
128
|
+
- headache.gemspec
|
129
|
+
- lib/headache.rb
|
130
|
+
- lib/headache/batch.rb
|
131
|
+
- lib/headache/definition/batch_control.rb
|
132
|
+
- lib/headache/definition/batch_header.rb
|
133
|
+
- lib/headache/definition/common.rb
|
134
|
+
- lib/headache/definition/entry.rb
|
135
|
+
- lib/headache/definition/file_control.rb
|
136
|
+
- lib/headache/definition/file_header.rb
|
137
|
+
- lib/headache/definitions.rb
|
138
|
+
- lib/headache/document.rb
|
139
|
+
- lib/headache/formatters.rb
|
140
|
+
- lib/headache/record/batch_control.rb
|
141
|
+
- lib/headache/record/batch_header.rb
|
142
|
+
- lib/headache/record/entry.rb
|
143
|
+
- lib/headache/record/file_control.rb
|
144
|
+
- lib/headache/record/file_header.rb
|
145
|
+
- lib/headache/record/overflow.rb
|
146
|
+
- lib/headache/records.rb
|
147
|
+
- lib/headache/version.rb
|
148
|
+
homepage: https://github.com/teampayoff/headache
|
149
|
+
licenses:
|
150
|
+
- MIT
|
151
|
+
metadata: {}
|
152
|
+
post_install_message:
|
153
|
+
rdoc_options: []
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
requirements: []
|
167
|
+
rubyforge_project:
|
168
|
+
rubygems_version: 2.4.8
|
169
|
+
signing_key:
|
170
|
+
specification_version: 4
|
171
|
+
summary: Take the pain out of building ACH files.
|
172
|
+
test_files: []
|