lsv-plus 1.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 +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+ [](https://circleci.com/gh/wtag/lsv-plus) [](https://codeclimate.com/github/wtag/lsv-plus) [](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
|