wvanbergen-clieop 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Willem van Bergen
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.
@@ -0,0 +1,22 @@
1
+ = CLIEOP
2
+
3
+ This library is a pure Ruby implementation of the CLIEOP03 format. Currently, it only supports writing
4
+ CLIEOP files with accounts receivable transactions. Accounts payable transactions, and reading existing
5
+ CLIEOP files is planned to be supported
6
+
7
+ This library is licensed under the MIT license.
8
+
9
+ == CLIEOP format
10
+
11
+ CLIEOP03 is a file format that is used for communication about cash transaction between bank accounts.
12
+ It is mainly used in the Netherlands and is supported by the banking software package of the main
13
+ banks. CLIEOP supports transactions in both directions: accounts payable and accounts receivable.
14
+ For accounts receivable, you need proper authorization from your bank.
15
+
16
+ For more information about the CLIEOP file format, see:
17
+ http://www.ingbank.nl/ing/downloadables/eclieop1.pdf
18
+
19
+ == Usage
20
+
21
+ You can create a file (<tt>Clieop::File</tt>), which can contain one or more batches (<tt>Clieop::Batch</tt>)
22
+ consisting of one or more records (<tt>Clieop::Record</tt>). There are different rcord types.
@@ -0,0 +1,73 @@
1
+ require 'rubygems'
2
+
3
+ load 'test/tasks.rake'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ namespace :gem do
9
+
10
+ desc "Sets the version and date of the clieop gem. Requires the VERSION environment variable."
11
+ task :version => [:manifest] do
12
+
13
+ require 'date'
14
+
15
+ new_version = ENV['VERSION']
16
+ raise "VERSION is required" unless /\d+(\.\d+)*/ =~ new_version
17
+
18
+ spec_file = Dir['*.gemspec'].first
19
+
20
+ spec = File.read(spec_file)
21
+ spec.gsub!(/^(\s*s\.version\s*=\s*)('|")(.+)('|")(\s*)$/) { "#{$1}'#{new_version}'#{$5}" }
22
+ spec.gsub!(/^(\s*s\.date\s*=\s*)('|")(.+)('|")(\s*)$/) { "#{$1}'#{Date.today.strftime('%Y-%m-%d')}'#{$5}" }
23
+ File.open(spec_file, 'w') { |f| f << spec }
24
+ end
25
+
26
+ task :tag => [:version] do
27
+
28
+ new_version = ENV['VERSION']
29
+ raise "VERSION is required" unless /\d+(\.\d+)*/ =~ new_version
30
+
31
+ spec_file = Dir['*.gemspec'].first
32
+
33
+ sh "git add #{spec_file} .manifest"
34
+ sh "git commit -m \"Set gem version to #{new_version}\""
35
+ sh "git push origin"
36
+ sh "git tag -a \"clieop-#{new_version}\" -m \"Tagged version #{new_version}\""
37
+ sh "git push --tags"
38
+ end
39
+
40
+ desc "Builds a ruby gem for the CLIEOP library"
41
+ task :build => [:manifest] do
42
+ spec_file = Dir['*.gemspec'].first
43
+ system "gem build #{spec_file}"
44
+ end
45
+
46
+ desc %{Update ".manifest" with the latest list of project filenames. Respect\
47
+ .gitignore by excluding everything that git ignores. Update `files` and\
48
+ `test_files` arrays in "*.gemspec" file if it's present.}
49
+ task :manifest do
50
+ list = Dir['**/*'].sort
51
+ spec_file = Dir['*.gemspec'].first
52
+ list -= [spec_file] if spec_file
53
+
54
+ File.read('.gitignore').each_line do |glob|
55
+ glob = glob.chomp.sub(/^\//, '')
56
+ list -= Dir[glob]
57
+ list -= Dir["#{glob}/**/*"] if File.directory?(glob) and !File.symlink?(glob)
58
+ puts "excluding #{glob}"
59
+ end
60
+
61
+ if spec_file
62
+ spec = File.read spec_file
63
+ spec.gsub! /^(\s* s.(test_)?files \s* = \s* )( \[ [^\]]* \] | %w\( [^)]* \) )/mx do
64
+ assignment = $1
65
+ bunch = $2 ? list.grep(/^test.*_test\.rb$/) : list
66
+ '%s%%w(%s)' % [assignment, bunch.join(' ')]
67
+ end
68
+
69
+ File.open(spec_file, 'w') {|f| f << spec }
70
+ end
71
+ File.open('.manifest', 'w') {|f| f << list.join("\n") }
72
+ end
73
+ end
data/TODO ADDED
@@ -0,0 +1,6 @@
1
+ - Add a Clieop::Transaction class
2
+ - Implement CLIEOP03 reader
3
+ - Add unit tests
4
+ - Test with additional banking software, both invoice and payment batches
5
+
6
+ Contact willem AT vanbergen DOT org if you want to hack on the CLIEOP library.
@@ -0,0 +1,4 @@
1
+ require 'date'
2
+ require File.dirname(__FILE__) + '/clieop/record.rb'
3
+ require File.dirname(__FILE__) + '/clieop/file.rb'
4
+ require File.dirname(__FILE__) + '/clieop/batch.rb'
@@ -0,0 +1,102 @@
1
+ module Clieop
2
+
3
+ class Batch
4
+
5
+ attr_accessor :transactions, :batch_info
6
+
7
+ def initialize(batch_info)
8
+ raise "Required: :description, :account_nr, :account_owner" unless ([:description, :account_nr, :account_owner] - batch_info.keys).empty?
9
+ @transactions = []
10
+ @batch_info = batch_info
11
+ end
12
+
13
+ # Adds a transaction to the batch
14
+ #
15
+ # :amount The amount involved with this transaction
16
+ # :account_nr The bank account from the other party
17
+ # :account_owner The name of the owner of the bank account
18
+ # :reference_number A reference number to identify this transaction
19
+ # :description A description for this transaction (4 lines max)
20
+ def add_transaction (transaction)
21
+ raise "No :account_nr given" if transaction[:account_nr].nil?
22
+ raise "No :amount given" if transaction[:amount].nil?
23
+ raise "No :account_owner given" if transaction[:account_owner].nil?
24
+ @transactions << transaction
25
+ end
26
+
27
+ alias_method :<<, :add_transaction
28
+
29
+ def to_clieop
30
+
31
+ # generate batch header records
32
+ batch_data = ""
33
+ batch_data << Clieop::Record.new(:batch_header,
34
+ :transaction_group => @batch_info[:transaction_group] || 0,
35
+ :acount_nr => @batch_info[:account_nr] || 0,
36
+ :serial_nr => @batch_info[:serial_nr] || 1,
37
+ :currency => @batch_info[:currency] || "EUR").to_clieop
38
+
39
+ batch_data << Clieop::Record.new(:batch_description, :description => @batch_info[:description]).to_clieop
40
+ batch_data << Clieop::Record.new(:batch_owner,
41
+ :process_date => @batch_info[:process_date] || 0,
42
+ :owner => @batch_info[:account_owner]).to_clieop
43
+
44
+ # initialize checksums
45
+ total_account = @batch_info[:account_nr].to_i * @transactions.length
46
+ total_amount = 0
47
+
48
+ # add transactions to batch
49
+ @transactions.each do |tr|
50
+
51
+ # update checksums
52
+ total_account += tr[:account_nr].to_i
53
+ total_amount += (tr[:amount].to_f * 100).truncate
54
+
55
+ # prepare data for this transaction's records
56
+ transaction_type = @batch_info[:transaction_group] == 10 ? 1002 : 0
57
+ to_account = @batch_info[:transaction_group] == 10 ? @batch_info[:account_nr] : tr[:account_nr]
58
+ from_account = @batch_info[:transaction_group] == 10 ? tr[:account_nr] : @batch_info[:account_nr]
59
+ amount_in_cents = (tr[:amount].to_f * 100).truncate
60
+ name_record = @batch_info[:transaction_group] == 10 ? :invoice_name : :payment_name
61
+
62
+ # generate transaction record
63
+ batch_data << Clieop::Record.new(:transaction_info,
64
+ :transaction_type => transaction_type, :amount => amount_in_cents,
65
+ :to_account => to_account, :from_account => from_account).to_clieop
66
+
67
+ # generate record with transaction information
68
+ batch_data << Clieop::Record.new(name_record, :name => tr[:account_owner]).to_clieop
69
+ batch_data << Clieop::Record.new(:transaction_reference, :reference_number => tr[:reference_number]).to_clieop unless tr[:reference_number].nil?
70
+
71
+ # split discription into lines and make a record for the first 4 lines
72
+ unless tr[:description].nil? || tr[:description] == ''
73
+ tr[:description].split(/\r?\n/)[0, 4].each do |line|
74
+ batch_data << Clieop::Record.new(:transaction_description, :description => line.strip).to_s unless line == ''
75
+ end
76
+ end
77
+ end
78
+
79
+ # generate batch footer record including some checks
80
+ batch_data << Clieop::Record.new(:batch_footer, :tranasction_count => @transactions.length,
81
+ :total_amount => total_amount, :account_checksum => total_account.to_s[0..10]).to_clieop
82
+
83
+ end
84
+
85
+ def to_s
86
+ self.to_clieop
87
+ end
88
+
89
+ # creates a batch for payments from a given account
90
+ def self.payment_batch(batch_info = {})
91
+ batch_info[:transaction_group] ||= 0
92
+ Clieop::Batch.new(batch_info)
93
+ end
94
+
95
+ # creates a batch for invoices to a given account
96
+ def self.invoice_batch(batch_info = {})
97
+ batch_info[:transaction_group] ||= 10
98
+ Clieop::Batch.new(batch_info)
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,39 @@
1
+ module Clieop
2
+
3
+ class File
4
+
5
+ attr_accessor :batches
6
+ attr_reader :file_info
7
+
8
+ def initialize(file_info = {})
9
+ file_info[:date] = Date.today unless file_info[:date]
10
+ file_info[:date] = file_info[:date].strftime('%d%m%y') if file_info[:date].respond_to?(:strftime)
11
+ @file_info = file_info
12
+ @batches = []
13
+ end
14
+
15
+ # renders this file as a CLIEOP03 formatted string
16
+ def to_clieop
17
+ clieop_data = Clieop::Record.new(:file_header, @file_info).to_clieop
18
+ @batches.each { |batch| clieop_data << batch.to_clieop }
19
+ clieop_data << Clieop::Record.new(:file_footer).to_clieop
20
+ end
21
+
22
+ def payment_batch(options)
23
+ @payment << Clieop::Batch.payment_batch(options, block)
24
+ yield(@batches.last) if block_given?
25
+ return @batches.last
26
+ end
27
+
28
+ def invoice_batch(options)
29
+ @batches << Clieop::Batch.invoice_batch(options)
30
+ yield(@batches.last) if block_given?
31
+ return @batches.last
32
+ end
33
+
34
+ # Alias for to_clieop
35
+ def to_s
36
+ self.to_clieop
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,115 @@
1
+ module Clieop
2
+
3
+ class Record
4
+
5
+ TYPE_DEFINITIONS = {
6
+ :file_header => [
7
+ [:record_code, :numeric, 4, 1],
8
+ [:record_variant, :alpha, 1, 'A'],
9
+ [:date, :numeric, 6],
10
+ [:filename, :alpha, 8, 'CLIEOP03'],
11
+ [:sender_identification, :alpha, 5],
12
+ [:file_identification, :alpha, 4],
13
+ [:duplicate_code, :numeric, 1, 1]
14
+ ],
15
+ :file_footer => [
16
+ [:record_code, :numeric, 4, 9999],
17
+ [:record_variant, :alpha, 1, 'A'],
18
+ ],
19
+ :batch_header => [
20
+ [:record_code, :numeric, 4, 10],
21
+ [:record_variant, :alpha, 1, 'B'],
22
+ [:transaction_group, :alpha, 2],
23
+ [:acount_nr, :numeric, 10],
24
+ [:serial_nr, :numeric, 4],
25
+ [:currency, :alpha, 3, 'EUR']
26
+ ],
27
+ :batch_footer => [
28
+ [:record_code, :numeric, 4, 9990],
29
+ [:record_variant, :alpha, 1, 'A'],
30
+ [:total_amount, :numeric, 18],
31
+ [:account_checksum, :numeric, 10],
32
+ [:tranasction_count, :numeric, 7],
33
+ ],
34
+ :batch_description => [
35
+ [:record_code, :numeric, 4, 20],
36
+ [:record_variant, :alpha, 1, 'A'],
37
+ [:description, :alpha, 32]
38
+ ],
39
+ :batch_owner => [
40
+ [:record_code, :numeric, 4, 30],
41
+ [:record_variant, :alpha, 1, 'B'],
42
+ [:naw_code, :numeric, 1, 1],
43
+ [:process_date, :numeric, 6, 0],
44
+ [:owner, :alpha, 35],
45
+ [:test, :alpha, 1, 'P']
46
+ ],
47
+ :transaction_info => [
48
+ [:record_code, :numeric, 4, 100],
49
+ [:record_variant, :alpha, 1, 'A'],
50
+ [:transaction_type, :numeric, 4, 1002],
51
+ [:amount, :numeric, 12],
52
+ [:from_account, :numeric, 10],
53
+ [:to_account, :numeric, 10],
54
+ ],
55
+ :invoice_name => [
56
+ [:record_code, :numeric, 4, 110],
57
+ [:record_variant, :alpha, 1, 'B'],
58
+ [:name, :alpha, 35],
59
+ ],
60
+ :payment_name =>[
61
+ [:record_code, :numeric, 4, 170],
62
+ [:record_variant, :alpha, 1, 'B'],
63
+ [:name, :alpha, 35],
64
+ ],
65
+ :transaction_reference => [
66
+ [:record_code, :numeric, 4, 150],
67
+ [:record_variant, :alpha, 1, 'A'],
68
+ [:reference_number, :numeric, 16],
69
+ ],
70
+ :transaction_description => [
71
+ [:record_code, :numeric, 4, 160],
72
+ [:record_variant, :alpha, 1, 'A'],
73
+ [:description, :alpha, 32],
74
+ ],
75
+ }
76
+
77
+ attr_accessor :definition, :data
78
+
79
+ def initialize record_type, record_data = {}
80
+
81
+ # load record definition
82
+ raise "Unknown record type" unless Clieop::Record::TYPE_DEFINITIONS[record_type.to_sym]
83
+ @definition = Clieop::Record::TYPE_DEFINITIONS[record_type.to_sym]
84
+
85
+ # set default values according to definition
86
+ @data = {}
87
+ @definition.each { |field| @data[field[0]] = field[3] if field[3] }
88
+
89
+ # set values for all the provided data
90
+ record_data.each { |field, value| @data[field] = value }
91
+ end
92
+
93
+ def to_clieop
94
+ line = ""
95
+ #format each field
96
+ @definition.each do |field|
97
+ fmt = '%'
98
+ fmt << (field[1] == :numeric ? '0' : '-')
99
+ fmt << (field[2].to_s)
100
+ fmt << (field[1] == :numeric ? 'd' : 's')
101
+ raw_data = (field[1] == :numeric) ? @data[field[0]].to_i : @data[field[0]]
102
+ value = sprintf(fmt, raw_data)
103
+ line << (field[1] == :numeric ? value[0 - field[2], field[2]] : value[0, field[2]])
104
+ end
105
+ # fill each line with spaces up to 50 characters and close with a CR/LF
106
+ line.ljust(50) + "\r\n"
107
+ end
108
+
109
+ def to_s
110
+ self.to_clieop
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -0,0 +1,32 @@
1
+ require 'test/unit'
2
+ require "#{File.dirname(__FILE__)}/../lib/clieop.rb"
3
+
4
+ class ClieopWriterTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ end
8
+
9
+ def teardown
10
+ end
11
+
12
+
13
+ def test_basic_invoice_usage
14
+ file = Clieop::File.new
15
+ file.invoice_batch({:description => 'some description', :account_nr => 123, :account_owner => 'me'}) do |batch|
16
+
17
+ batch << { :account_nr => 123456, :account_owner => 'you', :amount => 133.0,
18
+ :description => "Testing a CLIEOP direct debt transaction\nCharging your bank account" }
19
+
20
+ batch << { :account_nr => 654321, :account_owner => 'somebody else', :amount => 233.0,
21
+ :description => 'Some description for the other guy' }
22
+
23
+ end
24
+
25
+ clieop_data = file.to_clieop
26
+
27
+ #TODO: more tests
28
+ assert_kind_of String, clieop_data
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ desc 'Test the clieop library.'
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.pattern = 'test/**/*_test.rb'
6
+ t.verbose = true
7
+ t.libs << 'test'
8
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wvanbergen-clieop
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Willem van Bergen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-09-17 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: This library is a pure Ruby, MIT licensed implementation of the CLIEOP03 transaction format. CLIEOP03 can be used to communicate direct debt transactions with your (Dutch) bank.
17
+ email:
18
+ - willem@vanbergen.org
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - MIT-LICENSE
27
+ - README.rdoc
28
+ - Rakefile
29
+ - TODO
30
+ - lib
31
+ - lib/clieop
32
+ - lib/clieop.rb
33
+ - lib/clieop/batch.rb
34
+ - lib/clieop/file.rb
35
+ - lib/clieop/record.rb
36
+ - test
37
+ - test/clieop_writer_test.rb
38
+ - test/tasks.rake
39
+ has_rdoc: false
40
+ homepage: http://github.com/wvanbergen/clieop/wikis
41
+ post_install_message:
42
+ rdoc_options: []
43
+
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.2.0
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: A pure Ruby implementation to write CLIEOP files
65
+ test_files:
66
+ - test/clieop_writer_test.rb