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.
@@ -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