lsv-plus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 67b0dced0cbb671ce675eec190846010d2f54112
4
+ data.tar.gz: df55a6632071da0acd5cd8246585d8df817b2233
5
+ SHA512:
6
+ metadata.gz: fcf542e1fc21da168312cc9d29738c7e2c9cd7b4edc93bea047146e0cdce830262c16bd78d6695a8a7991be30744ac37e67e7c8f7a4a76f8995df83358cdcdc6
7
+ data.tar.gz: f075b7d881fdc12bdd9e459b32bcd1fea36e637a8859c8bcf1f1d9728cc6cf513a7ed817c6a671d766a5b8e7a9816369e3ec9d0a945e2a5579139a55e3ec71c6
@@ -0,0 +1,2 @@
1
+ spec/examples.txt
2
+ coverage
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1 @@
1
+ 2.2.3
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,71 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lsv-plus (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ builder (3.2.2)
10
+ coveralls (0.8.10)
11
+ json (~> 1.8)
12
+ rest-client (>= 1.6.8, < 2)
13
+ simplecov (~> 0.11.0)
14
+ term-ansicolor (~> 1.3)
15
+ thor (~> 0.19.1)
16
+ tins (~> 1.6.0)
17
+ diff-lcs (1.2.5)
18
+ docile (1.1.5)
19
+ domain_name (0.5.25)
20
+ unf (>= 0.0.5, < 1.0.0)
21
+ http-cookie (1.0.2)
22
+ domain_name (~> 0.5)
23
+ json (1.8.3)
24
+ mime-types (2.99)
25
+ netrc (0.11.0)
26
+ rake (10.4.2)
27
+ rest-client (1.8.0)
28
+ http-cookie (>= 1.0.2, < 2.0)
29
+ mime-types (>= 1.16, < 3.0)
30
+ netrc (~> 0.7)
31
+ rspec (3.4.0)
32
+ rspec-core (~> 3.4.0)
33
+ rspec-expectations (~> 3.4.0)
34
+ rspec-mocks (~> 3.4.0)
35
+ rspec-core (3.4.1)
36
+ rspec-support (~> 3.4.0)
37
+ rspec-expectations (3.4.0)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.4.0)
40
+ rspec-mocks (3.4.0)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.4.0)
43
+ rspec-support (3.4.1)
44
+ rspec_junit_formatter (0.2.3)
45
+ builder (< 4)
46
+ rspec-core (>= 2, < 4, != 2.12.0)
47
+ simplecov (0.11.1)
48
+ docile (~> 1.1.0)
49
+ json (~> 1.8)
50
+ simplecov-html (~> 0.10.0)
51
+ simplecov-html (0.10.0)
52
+ term-ansicolor (1.3.2)
53
+ tins (~> 1.0)
54
+ thor (0.19.1)
55
+ tins (1.6.0)
56
+ unf (0.1.4)
57
+ unf_ext
58
+ unf_ext (0.0.7.1)
59
+
60
+ PLATFORMS
61
+ ruby
62
+
63
+ DEPENDENCIES
64
+ coveralls (>= 0.8.10)
65
+ lsv-plus!
66
+ rake (~> 10.4)
67
+ rspec (~> 3.4.0)
68
+ rspec_junit_formatter (>= 0.2.2)
69
+
70
+ BUNDLED WITH
71
+ 1.10.6
@@ -0,0 +1,69 @@
1
+ # LSV+ [![Circle CI](https://circleci.com/gh/wtag/lsv-plus.svg?style=svg)](https://circleci.com/gh/wtag/lsv-plus) [![Code Climate](https://codeclimate.com/github/wtag/lsv-plus/badges/gpa.svg)](https://codeclimate.com/github/wtag/lsv-plus) [![Coverage Status](https://coveralls.io/repos/wtag/lsv-plus/badge.svg?branch=master&service=github)](https://coveralls.io/github/wtag/lsv-plus?branch=master)
2
+
3
+ Create LSV+ files with ease.
4
+
5
+ ## Setup
6
+
7
+ Add the gem to your project:
8
+
9
+ ```ruby
10
+ gem 'lsv-plus'
11
+ ```
12
+
13
+ Run bundle to install:
14
+
15
+ ```shell
16
+ $ bundle install
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ To create a new LSV+ file, follow the steps below.
22
+
23
+ ```ruby
24
+ # instantiate a new LSV+ file
25
+ file = LSVplus::File.new(
26
+ creator_identification: 'WTAGI',
27
+ currency: 'CHF',
28
+ processing_type: 'P',
29
+ creation_date: Date.today,
30
+ lsv_identification: 'WT001',
31
+ )
32
+
33
+ # add some records to the file
34
+ record1 = LSVplus::Record.new(
35
+ processing_date: Date.today + 1,
36
+ creditor_bank_clearing_number: 1337,
37
+ amount: BigDecimal.new('1337.42'),
38
+ debitor_bank_clearing_number: 42,
39
+ creditor_iban: 'CH9300762011623852957',
40
+ creditor_address: ['Fancy AG', 'Funnystreet 42'],
41
+ debitor_account: '123.456-78XY',
42
+ debitor_address: ['Debit AG', 'Other Street 1337', 'Somewhere City'],
43
+ message: ['Invoice 133 via BDD'],
44
+ reference_type: 'A',
45
+ reference: '200002000000004443332000061',
46
+ esr_member_id: '133742',
47
+ )
48
+ file.add_record record1
49
+
50
+ record2 = LSVplus::Record.new(
51
+ processing_date: Date.today + 1,
52
+ creditor_bank_clearing_number: 1337,
53
+ amount: BigDecimal.new('42.10'),
54
+ debitor_bank_clearing_number: 1337,
55
+ creditor_iban: 'CH9300762011623852957',
56
+ creditor_address: ['Fancy AG', 'Funnystreet 42'],
57
+ debitor_account: '455.24401-AB',
58
+ debitor_address: ['Customer 77', 'Bubu Av', 'New Lala City'],
59
+ message: ['Invoice 42 via BDD'],
60
+ reference_type: 'B',
61
+ reference: '030000SWAGFEEFORYOLO',
62
+ esr_member_id: nil,
63
+ )
64
+ file.add_record record2
65
+
66
+ # generate the LSV+ file and return it as a string
67
+ file.to_s
68
+ # => "8750P201601071337 2016010642 WTAGI0000001WT001CHF000001337,42CH9300762011623852957 Fancy AG Funnystreet 42 123.456-78XY Debit AG Other Street 1337 Somewhere City Invoice 133 via BDD A200002000000004443332000061133742 8750P201601071337 201601061337 WTAGI0000002WT001CHF000000042,10CH9300762011623852957 Fancy AG Funnystreet 42 455.24401-AB Customer 77 Bubu Av New Lala City Invoice 42 via BDD B030000SWAGFEEFORYOLO 890020160106WTAGI0000002CHF000001379,52"
69
+ ```
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift './lib'
4
+
5
+ require 'irb'
6
+ require 'lsv-plus'
7
+
8
+ IRB.start
@@ -0,0 +1 @@
1
+ require 'lsv_plus'
@@ -0,0 +1,7 @@
1
+ require 'date'
2
+
3
+ module LSVplus
4
+ end
5
+
6
+ require 'lsv_plus/file'
7
+ require 'lsv_plus/record'
@@ -0,0 +1,47 @@
1
+ require 'lsv_plus/errors'
2
+
3
+ module LSVplus
4
+ class BaseContainer
5
+ class UnknownAttribute < LSVplus::Errors::Base
6
+ def initialize(attribute)
7
+ @attribute = attribute
8
+ end
9
+
10
+ def to_s
11
+ "Attribute #{@attribute} is unknown"
12
+ end
13
+ end
14
+
15
+ class MissingAttribute < LSVplus::Errors::Base
16
+ def initialize(attribute)
17
+ @attribute = attribute
18
+ end
19
+
20
+ def to_s
21
+ "Attribute :#{@attribute} is missing"
22
+ end
23
+ end
24
+
25
+ def initialize(attributes)
26
+ validate attributes
27
+ set_attributes attributes
28
+ end
29
+
30
+ def validate(attributes)
31
+ validate_presence_of_required_attributes(attributes)
32
+ end
33
+
34
+ def set_attributes(attributes)
35
+ attributes.each do |key, value|
36
+ raise UnknownAttribute, key unless self.class::ATTRIBUTES.include?(key)
37
+ instance_variable_set("@#{key}", value)
38
+ end
39
+ end
40
+
41
+ def validate_presence_of_required_attributes(attributes)
42
+ self.class::ATTRIBUTES.each do |attribute|
43
+ raise MissingAttribute, attribute unless attributes.key?(attribute)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,14 @@
1
+ module LSVplus
2
+ module Errors
3
+ class Base < StandardError; end
4
+ class InvalidAttribute < Base
5
+ def initialize(reason)
6
+ @reason = reason
7
+ end
8
+
9
+ def to_s
10
+ "Attribute #{self.class::ATTRIBUTE} is invalid: #{@reason}"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,68 @@
1
+ require 'lsv_plus/errors'
2
+ require 'lsv_plus/base_container'
3
+ require 'lsv_plus/record_formatter'
4
+ require 'lsv_plus/total_record_formatter'
5
+
6
+ module LSVplus
7
+ class File < LSVplus::BaseContainer
8
+ class InvalidCreatorIdentification < LSVplus::Errors::InvalidAttribute
9
+ ATTRIBUTE = :creator_identification
10
+ end
11
+ class InvalidLSVIdentification < LSVplus::Errors::InvalidAttribute
12
+ ATTRIBUTE = :lsv_identification
13
+ end
14
+
15
+ ATTRIBUTES = %i(processing_type creation_date creator_identification currency lsv_identification)
16
+ FIVE_CHARS_UPPERCASE = /\A[A-Z0-9]{5}\z/
17
+
18
+ attr_reader(*ATTRIBUTES)
19
+
20
+ def add_record(record)
21
+ records << record
22
+ end
23
+
24
+ def records
25
+ @records ||= []
26
+ end
27
+
28
+ def to_s
29
+ records_as_string + total_record
30
+ end
31
+
32
+ def records_as_string
33
+ output = StringIO.new
34
+ records.each_with_index do |record, index|
35
+ index += 1
36
+ output.write RecordFormatter.call(self, record, index)
37
+ end
38
+ output.rewind
39
+ output.read
40
+ end
41
+
42
+ def total_record
43
+ TotalRecordFormatter.call(self)
44
+ end
45
+
46
+ def total
47
+ records.inject(BigDecimal.new(0)) { |sum, record| sum + record.amount }
48
+ end
49
+
50
+ def validate(attributes)
51
+ super(attributes)
52
+ validate_creator_identification(attributes)
53
+ validate_lsv_identification(attributes)
54
+ end
55
+
56
+ def validate_creator_identification(attributes)
57
+ unless attributes[:creator_identification] =~ FIVE_CHARS_UPPERCASE
58
+ raise InvalidCreatorIdentification, "Does not match #{FIVE_CHARS_UPPERCASE}"
59
+ end
60
+ end
61
+
62
+ def validate_lsv_identification(attributes)
63
+ unless attributes[:lsv_identification] =~ FIVE_CHARS_UPPERCASE
64
+ raise InvalidLSVIdentification, "Does not match #{FIVE_CHARS_UPPERCASE}"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,34 @@
1
+ module LSVplus
2
+ module FormattingHelper
3
+ DATE_FORMAT = '%Y%m%d'
4
+
5
+ def self.date(date)
6
+ date.strftime DATE_FORMAT
7
+ end
8
+
9
+ def self.index(index)
10
+ format '%07d', index
11
+ end
12
+
13
+ def self.amount(amount)
14
+ format('%012.2f', amount).sub('.', ',')
15
+ end
16
+
17
+ def self.multiline(lines)
18
+ lines_string = StringIO.new
19
+ 4.times do |index|
20
+ lines_string.write format('%-35s', lines[index])
21
+ end
22
+ lines_string.rewind
23
+ lines_string.read
24
+ end
25
+
26
+ def self.account(account)
27
+ format '%-34s', account
28
+ end
29
+
30
+ def self.clearing_number(number)
31
+ format '%-5s', number
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,57 @@
1
+ require 'bigdecimal'
2
+ require 'lsv_plus/base_container'
3
+ require 'lsv_plus/errors'
4
+
5
+ module LSVplus
6
+ class Record < LSVplus::BaseContainer
7
+ class InvalidProcessingDate < LSVplus::Errors::InvalidAttribute
8
+ ATTRIBUTE = :processing_date
9
+ end
10
+ class InvalidAmount < LSVplus::Errors::InvalidAttribute
11
+ ATTRIBUTE = :amount
12
+ end
13
+
14
+ ATTRIBUTES = %i(
15
+ type version
16
+ creditor_bank_clearing_number creditor_iban creditor_address
17
+ debitor_bank_clearing_number debitor_account debitor_address
18
+ amount message processing_date
19
+ reference_type reference esr_member_id
20
+ )
21
+
22
+ TYPE = '875'
23
+ VERSION = '0'
24
+ MAX_AMOUNT = BigDecimal.new('99_999_999.99')
25
+ PROCESSING_MAX_IN_FUTURE = 30
26
+ PROCESSING_MAX_IN_PAST = 10
27
+
28
+ attr_reader(*ATTRIBUTES)
29
+
30
+ def initialize(attributes)
31
+ attributes = attributes.merge type: TYPE, version: VERSION
32
+ validate attributes
33
+ super attributes
34
+ end
35
+
36
+ def validate(attributes)
37
+ super(attributes)
38
+ validate_processing_date(attributes)
39
+ validate_amount(attributes)
40
+ end
41
+
42
+ def validate_processing_date(attributes)
43
+ if Date.today + PROCESSING_MAX_IN_FUTURE < attributes[:processing_date]
44
+ raise InvalidProcessingDate, "Max #{PROCESSING_MAX_IN_FUTURE} days in future"
45
+ end
46
+ if Date.today - PROCESSING_MAX_IN_PAST > attributes[:processing_date]
47
+ raise InvalidProcessingDate, "Max #{PROCESSING_MAX_IN_PAST} days in past"
48
+ end
49
+ end
50
+
51
+ def validate_amount(attributes)
52
+ if attributes[:amount] > MAX_AMOUNT
53
+ raise InvalidAmount, "Must not be higher than #{MAX_AMOUNT}"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,110 @@
1
+ require 'lsv_plus/formatting_helper'
2
+
3
+ module LSVplus
4
+ class RecordFormatter
5
+ attr_reader :file, :record, :output
6
+
7
+ def self.call(file, record, index)
8
+ new(file, record, index).call
9
+ end
10
+
11
+ def initialize(file, record, index)
12
+ @file = file
13
+ @record = record
14
+ @index = index
15
+ @output = StringIO.new
16
+ end
17
+
18
+ def call
19
+ [
20
+ type, version, processing_type, processing_date,
21
+ creditor_bank_clearing_number, creation_date, debitor_bank_clearing_number,
22
+ creator_identification, record_number, lsv_identification,
23
+ currency, amount,
24
+ creditor_iban, creditor_address,
25
+ debitor_account, debitor_address,
26
+ message, reference_type, reference, esr_member_id
27
+ ].join('')
28
+ end
29
+
30
+ def type
31
+ record.type
32
+ end
33
+
34
+ def version
35
+ record.version
36
+ end
37
+
38
+ def processing_type
39
+ file.processing_type
40
+ end
41
+
42
+ def processing_date
43
+ LSVplus::FormattingHelper.date record.processing_date
44
+ end
45
+
46
+ def creditor_bank_clearing_number
47
+ LSVplus::FormattingHelper.clearing_number record.creditor_bank_clearing_number
48
+ end
49
+
50
+ def creation_date
51
+ LSVplus::FormattingHelper.date file.creation_date
52
+ end
53
+
54
+ def debitor_bank_clearing_number
55
+ LSVplus::FormattingHelper.clearing_number record.debitor_bank_clearing_number
56
+ end
57
+
58
+ def creator_identification
59
+ file.creator_identification
60
+ end
61
+
62
+ def record_number
63
+ LSVplus::FormattingHelper.index @index
64
+ end
65
+
66
+ def lsv_identification
67
+ file.lsv_identification
68
+ end
69
+
70
+ def currency
71
+ file.currency
72
+ end
73
+
74
+ def amount
75
+ LSVplus::FormattingHelper.amount record.amount
76
+ end
77
+
78
+ def creditor_iban
79
+ LSVplus::FormattingHelper.account record.creditor_iban
80
+ end
81
+
82
+ def creditor_address
83
+ LSVplus::FormattingHelper.multiline record.creditor_address
84
+ end
85
+
86
+ def debitor_account
87
+ LSVplus::FormattingHelper.account record.debitor_account
88
+ end
89
+
90
+ def debitor_address
91
+ LSVplus::FormattingHelper.multiline record.debitor_address
92
+ end
93
+
94
+ def message
95
+ LSVplus::FormattingHelper.multiline record.message
96
+ end
97
+
98
+ def reference_type
99
+ record.reference_type
100
+ end
101
+
102
+ def reference
103
+ format '%-27s', record.reference
104
+ end
105
+
106
+ def esr_member_id
107
+ format '%-9s', record.esr_member_id
108
+ end
109
+ end
110
+ end