lsv-plus 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +71 -0
- data/README.md +69 -0
- data/bin/lsv-plus +8 -0
- data/lib/lsv-plus.rb +1 -0
- data/lib/lsv_plus.rb +7 -0
- data/lib/lsv_plus/base_container.rb +47 -0
- data/lib/lsv_plus/errors.rb +14 -0
- data/lib/lsv_plus/file.rb +68 -0
- data/lib/lsv_plus/formatting_helper.rb +34 -0
- data/lib/lsv_plus/record.rb +57 -0
- data/lib/lsv_plus/record_formatter.rb +110 -0
- data/lib/lsv_plus/total_record_formatter.rb +42 -0
- data/lib/lsv_plus/version.rb +3 -0
- data/lsv-plus.gemspec +25 -0
- data/spec/file_spec.rb +98 -0
- data/spec/formatting_helper_spec.rb +51 -0
- data/spec/integration/simple_spec.rb +97 -0
- data/spec/record_formatter_spec.rb +313 -0
- data/spec/record_spec.rb +62 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/total_record_formatter_spec.rb +90 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.3
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -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
|
data/README.md
ADDED
@@ -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
|
+
```
|
data/bin/lsv-plus
ADDED
data/lib/lsv-plus.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'lsv_plus'
|
data/lib/lsv_plus.rb
ADDED
@@ -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,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
|