sepa_file_parser 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/sepa_file_parser/camt052/base.rb +18 -0
- data/lib/sepa_file_parser/camt052/report.rb +73 -0
- data/lib/sepa_file_parser/camt053/base.rb +18 -0
- data/lib/sepa_file_parser/camt053/statement.rb +75 -0
- data/lib/sepa_file_parser/camt054/base.rb +18 -0
- data/lib/sepa_file_parser/camt054/notification.rb +54 -0
- data/lib/sepa_file_parser/errors.rb +12 -0
- data/lib/sepa_file_parser/file.rb +10 -0
- data/lib/sepa_file_parser/general/account.rb +66 -0
- data/lib/sepa_file_parser/general/account_balance.rb +61 -0
- data/lib/sepa_file_parser/general/batch_detail.rb +24 -0
- data/lib/sepa_file_parser/general/charges.rb +25 -0
- data/lib/sepa_file_parser/general/creditor.rb +46 -0
- data/lib/sepa_file_parser/general/debitor.rb +46 -0
- data/lib/sepa_file_parser/general/entry.rb +117 -0
- data/lib/sepa_file_parser/general/group_header.rb +38 -0
- data/lib/sepa_file_parser/general/pain_entry.rb +46 -0
- data/lib/sepa_file_parser/general/postal_address.rb +45 -0
- data/lib/sepa_file_parser/general/record.rb +49 -0
- data/lib/sepa_file_parser/general/transaction.rb +139 -0
- data/lib/sepa_file_parser/general/type/builder.rb +20 -0
- data/lib/sepa_file_parser/general/type/code.rb +13 -0
- data/lib/sepa_file_parser/general/type/proprietary.rb +13 -0
- data/lib/sepa_file_parser/misc.rb +28 -0
- data/lib/sepa_file_parser/pain001/base.rb +18 -0
- data/lib/sepa_file_parser/pain001/payment_information.rb +44 -0
- data/lib/sepa_file_parser/pain008/base.rb +18 -0
- data/lib/sepa_file_parser/pain008/payment_information.rb +44 -0
- data/lib/sepa_file_parser/register.rb +23 -0
- data/lib/sepa_file_parser/string.rb +10 -0
- data/lib/sepa_file_parser/version.rb +5 -0
- data/lib/sepa_file_parser/xml.rb +45 -0
- data/lib/sepa_file_parser.rb +38 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5233f32c897d7e33ce7a00b1213f4933fed2c6d9d3c47d2c4367268e21f5dccd
|
4
|
+
data.tar.gz: 5562ca231c07df7848143dfa5f0d7b1dde9fc81ebf866ad18eb55b346596bf04
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b151cf8b849f21e33e01d4c2b85268c150cf010ab0c62a5743dd9ef004ebac010b5f46f237d6b9c3ad5764aa26ca6eeda27a1bb6fe45349fa810259e560baa2f
|
7
|
+
data.tar.gz: b7557e088a656bc18f892f008e7aba228efbdc60a0da2b998735c9db0d7935b9babecbf25679618d60683f64cf1a318807c0c3045c3abf1804eb908fabc423ab
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
module Camt052
|
5
|
+
class Base
|
6
|
+
attr_reader :group_header, :reports, :xml_data
|
7
|
+
|
8
|
+
def initialize(xml_data)
|
9
|
+
@xml_data = xml_data
|
10
|
+
# BkToCstmrAccptRpt = Bank to Customer Account Report
|
11
|
+
grphdr = xml_data.xpath('BkToCstmrAcctRpt/GrpHdr')
|
12
|
+
@group_header = SepaFileParser::GroupHeader.new(grphdr)
|
13
|
+
reports = xml_data.xpath('BkToCstmrAcctRpt/Rpt')
|
14
|
+
@reports = reports.map{ |x| Report.new(x) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
module Camt052
|
5
|
+
class Report
|
6
|
+
|
7
|
+
attr_reader :xml_data
|
8
|
+
|
9
|
+
def initialize(xml_data)
|
10
|
+
@xml_data = xml_data
|
11
|
+
end
|
12
|
+
|
13
|
+
def identification
|
14
|
+
@identification ||= xml_data.xpath('Id/text()').text
|
15
|
+
end
|
16
|
+
|
17
|
+
def generation_date
|
18
|
+
@generation_date ||= Time.parse(xml_data.xpath('CreDtTm/text()').text)
|
19
|
+
end
|
20
|
+
|
21
|
+
def account
|
22
|
+
@account ||= SepaFileParser::Account.from_camt_data(xml_data.xpath('Acct').first)
|
23
|
+
end
|
24
|
+
|
25
|
+
def entries
|
26
|
+
@entries ||= xml_data.xpath('Ntry').map{ |x| SepaFileParser::Entry.new(x) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def legal_sequence_number
|
30
|
+
@legal_sequence_number ||= xml_data.xpath('LglSeqNb/text()').text
|
31
|
+
end
|
32
|
+
|
33
|
+
def from_date_time
|
34
|
+
@from_date_time ||= (x = xml_data.xpath('FrToDt/FrDtTm')).empty? ? nil : Time.parse(x.first.content)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_date_time
|
38
|
+
@to_date_time ||= (x = xml_data.xpath('FrToDt/ToDtTm')).empty? ? nil : Time.parse(x.first.content)
|
39
|
+
end
|
40
|
+
|
41
|
+
def opening_balance
|
42
|
+
@opening_balance ||= begin
|
43
|
+
bal = xml_data.xpath('Bal/Tp//Cd[contains(text(), "PRCD")]').first.ancestors('Bal')
|
44
|
+
date = bal.xpath('Dt/Dt/text()').text
|
45
|
+
credit = bal.xpath('CdtDbtInd/text()').text == 'CRDT'
|
46
|
+
currency = bal.xpath('Amt').attribute('Ccy').value
|
47
|
+
SepaFileParser::AccountBalance.new bal.xpath('Amt/text()').text, currency, date, credit
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias_method :opening_or_intermediary_balance, :opening_balance
|
51
|
+
|
52
|
+
def closing_balance
|
53
|
+
@closing_balance ||= begin
|
54
|
+
bal = xml_data.xpath('Bal/Tp//Cd[contains(text(), "CLBD")]').first.ancestors('Bal')
|
55
|
+
date = bal.xpath('Dt/Dt/text()').text
|
56
|
+
credit = bal.xpath('CdtDbtInd/text()').text == 'CRDT'
|
57
|
+
currency = bal.xpath('Amt').attribute('Ccy').value
|
58
|
+
SepaFileParser::AccountBalance.new bal.xpath('Amt/text()').text, currency, date, credit
|
59
|
+
end
|
60
|
+
end
|
61
|
+
alias_method :closing_or_intermediary_balance, :closing_balance
|
62
|
+
|
63
|
+
|
64
|
+
def source
|
65
|
+
xml_data.to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.parse(xml)
|
69
|
+
self.new Nokogiri::XML(xml).xpath('Report')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
module Camt053
|
5
|
+
class Base
|
6
|
+
attr_reader :group_header, :statements, :xml_data
|
7
|
+
|
8
|
+
def initialize(xml_data)
|
9
|
+
@xml_data = xml_data
|
10
|
+
|
11
|
+
grphdr = xml_data.xpath('BkToCstmrStmt/GrpHdr')
|
12
|
+
@group_header = GroupHeader.new(grphdr)
|
13
|
+
statements = xml_data.xpath('BkToCstmrStmt/Stmt')
|
14
|
+
@statements = statements.map{ |x| Statement.new(x) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
module Camt053
|
5
|
+
class Statement
|
6
|
+
|
7
|
+
attr_reader :xml_data
|
8
|
+
|
9
|
+
def initialize(xml_data)
|
10
|
+
@xml_data = xml_data
|
11
|
+
end
|
12
|
+
|
13
|
+
def identification
|
14
|
+
@identification ||= xml_data.xpath('Id/text()').text
|
15
|
+
end
|
16
|
+
|
17
|
+
def generation_date
|
18
|
+
@generation_date ||= Time.parse(xml_data.xpath('CreDtTm/text()').text)
|
19
|
+
end
|
20
|
+
|
21
|
+
def from_date_time
|
22
|
+
@from_date_time ||= (x = xml_data.xpath('FrToDt/FrDtTm')).empty? ? nil : Time.parse(x.first.content)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_date_time
|
26
|
+
@to_date_time ||= (x = xml_data.xpath('FrToDt/ToDtTm')).empty? ? nil : Time.parse(x.first.content)
|
27
|
+
end
|
28
|
+
|
29
|
+
def account
|
30
|
+
@account ||= SepaFileParser::Account.from_camt_data(xml_data.xpath('Acct').first)
|
31
|
+
end
|
32
|
+
|
33
|
+
def entries
|
34
|
+
@entries ||= xml_data.xpath('Ntry').map{ |x| Entry.new(x) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def legal_sequence_number
|
38
|
+
@legal_sequence_number ||= xml_data.xpath('LglSeqNb/text()').text
|
39
|
+
end
|
40
|
+
|
41
|
+
def electronic_sequence_number
|
42
|
+
@electronic_sequence_number ||= xml_data.xpath('ElctrncSeqNb/text()').text
|
43
|
+
end
|
44
|
+
|
45
|
+
def opening_balance
|
46
|
+
@opening_balance ||= begin
|
47
|
+
bal = xml_data.xpath('Bal/Tp//Cd[contains(text(), "OPBD") or contains(text(), "PRCD")]').first.ancestors('Bal')
|
48
|
+
date = bal.xpath('Dt/Dt/text()').text
|
49
|
+
credit = bal.xpath('CdtDbtInd/text()').text == 'CRDT'
|
50
|
+
currency = bal.xpath('Amt').attribute('Ccy').value
|
51
|
+
AccountBalance.new bal.xpath('Amt/text()').text, currency, date, credit
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def closing_balance
|
56
|
+
@closing_balance ||= begin
|
57
|
+
bal = xml_data.xpath('Bal/Tp//Cd[contains(text(), "CLBD")]').first.ancestors('Bal')
|
58
|
+
date = bal.xpath('Dt/Dt/text()').text
|
59
|
+
credit = bal.xpath('CdtDbtInd/text()').text == 'CRDT'
|
60
|
+
currency = bal.xpath('Amt').attribute('Ccy').value
|
61
|
+
AccountBalance.new bal.xpath('Amt/text()').text, currency, date, credit
|
62
|
+
end
|
63
|
+
end
|
64
|
+
alias_method :closing_or_intermediary_balance, :closing_balance
|
65
|
+
|
66
|
+
def source
|
67
|
+
xml_data.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.parse(xml)
|
71
|
+
self.new Nokogiri::XML(xml).xpath('Stmt')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
module Camt054
|
5
|
+
class Base
|
6
|
+
attr_reader :group_header, :notifications, :xml_data
|
7
|
+
|
8
|
+
def initialize(xml_data)
|
9
|
+
@xml_data = xml_data
|
10
|
+
|
11
|
+
grphdr = xml_data.xpath('BkToCstmrDbtCdtNtfctn/GrpHdr')
|
12
|
+
@group_header = GroupHeader.new(grphdr)
|
13
|
+
notifications = xml_data.xpath('BkToCstmrDbtCdtNtfctn/Ntfctn')
|
14
|
+
@notifications = notifications.map{ |x| Notification.new(x) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
module Camt054
|
5
|
+
class Notification
|
6
|
+
|
7
|
+
attr_reader :xml_data
|
8
|
+
|
9
|
+
def initialize(xml_data)
|
10
|
+
@xml_data = xml_data
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
def identification
|
15
|
+
@identification ||= xml_data.xpath('Id/text()').text
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Time]
|
19
|
+
def generation_date
|
20
|
+
@generation_date ||= Time.parse(xml_data.xpath('CreDtTm/text()').text)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Time, nil]
|
24
|
+
def from_date_time
|
25
|
+
@from_date_time ||= (x = xml_data.xpath('FrToDt/FrDtTm')).empty? ? nil : Time.parse(x.first.content)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Time, nil]
|
29
|
+
def to_date_time
|
30
|
+
@to_date_time ||= (x = xml_data.xpath('FrToDt/ToDtTm')).empty? ? nil : Time.parse(x.first.content)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Account]
|
34
|
+
def account
|
35
|
+
@account ||= SepaFileParser::Account.from_camt_data(xml_data.xpath('Acct').first)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Array<Entry>]
|
39
|
+
def entries
|
40
|
+
@entries ||= xml_data.xpath('Ntry').map{ |x| Entry.new(x) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [String]
|
44
|
+
def source
|
45
|
+
xml_data.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [SepaFileParser::Camt054::Notification]
|
49
|
+
def self.parse(xml)
|
50
|
+
self.new Nokogiri::XML(xml).xpath('Ntfctn')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
module Errors
|
5
|
+
class BaseError < StandardError; end
|
6
|
+
|
7
|
+
class NamespaceAlreadyRegistered < BaseError; end
|
8
|
+
class NotXMLError < BaseError; end
|
9
|
+
class UnsupportedParserClass < BaseError; end
|
10
|
+
class UnsupportedNamespaceError < BaseError; end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
class Account
|
5
|
+
|
6
|
+
def self.from_camt_data(xml_data)
|
7
|
+
self.new(
|
8
|
+
iban: xml_data.xpath('Id/IBAN/text()').text,
|
9
|
+
other_id: xml_data.xpath('Id/Othr/Id/text()').text,
|
10
|
+
bic: [
|
11
|
+
xml_data.xpath('Svcr/FinInstnId/BIC/text()').text,
|
12
|
+
xml_data.xpath('Svcr/FinInstnId/BICFI/text()').text,
|
13
|
+
].reject(&:empty?).first.to_s,
|
14
|
+
bank_name: xml_data.xpath('Svcr/FinInstnId/Nm/text()').text,
|
15
|
+
currency: xml_data.xpath('Ccy/text()').text,
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.from_pain_data(xml_data, currency, context)
|
20
|
+
self.new(
|
21
|
+
iban: xml_data.xpath("#{context}Acct/Id/IBAN/text()").text,
|
22
|
+
bic: xml_data.xpath("#{context}Agt/FinInstnId/BIC/text()").text,
|
23
|
+
bank_name: xml_data.xpath("#{context}/Nm/text()").text,
|
24
|
+
currency: currency,
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(iban: nil, other_id: nil, bic:, bank_name:, currency:)
|
29
|
+
@iban = iban
|
30
|
+
@other_id = other_id
|
31
|
+
@bic = bic
|
32
|
+
@bank_name = bank_name
|
33
|
+
@currency = currency
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String]
|
37
|
+
def iban
|
38
|
+
@iban.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String]
|
42
|
+
def other_id
|
43
|
+
@other_id.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [String]
|
47
|
+
def account_number
|
48
|
+
iban != '' ? iban : other_id
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String]
|
52
|
+
def bic
|
53
|
+
@bic.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [String]
|
57
|
+
def bank_name
|
58
|
+
@bank_name.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [String]
|
62
|
+
def currency
|
63
|
+
@currency.to_s
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
class AccountBalance
|
5
|
+
|
6
|
+
# @param amount [String]
|
7
|
+
# @param currency [String]
|
8
|
+
# @param date [String]
|
9
|
+
# @param credit [Boolean]
|
10
|
+
def initialize(amount, currency, date, credit = false)
|
11
|
+
@amount = amount
|
12
|
+
@currency = currency
|
13
|
+
@date = date
|
14
|
+
@credit = credit
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String]
|
18
|
+
def currency
|
19
|
+
@currency
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Date]
|
23
|
+
def date
|
24
|
+
Date.parse @date
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Integer] either 1 or -1
|
28
|
+
def sign
|
29
|
+
credit? ? 1 : -1
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Boolean]
|
33
|
+
def credit?
|
34
|
+
@credit
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [BigDecimal]
|
38
|
+
def amount
|
39
|
+
SepaFileParser::Misc.to_amount(@amount)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Integer]
|
43
|
+
def amount_in_cents
|
44
|
+
SepaFileParser::Misc.to_amount_in_cents(@amount)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [BigDecimal]
|
48
|
+
def signed_amount
|
49
|
+
amount * sign
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Hash{String => BigDecimal, Integer}]
|
53
|
+
def to_h
|
54
|
+
{
|
55
|
+
'amount' => amount,
|
56
|
+
'amount_in_cents' => amount_in_cents,
|
57
|
+
'sign' => sign
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
class BatchDetail
|
5
|
+
|
6
|
+
attr_reader :xml_data
|
7
|
+
|
8
|
+
def initialize(xml_data)
|
9
|
+
@xml_data = xml_data
|
10
|
+
end
|
11
|
+
|
12
|
+
def payment_information_identification
|
13
|
+
@payment_information_identification ||= xml_data.xpath('PmtInfId/text()').text
|
14
|
+
end
|
15
|
+
|
16
|
+
def msg_id # may be missing
|
17
|
+
@msg_id ||= xml_data.xpath('MsgId/text()').text
|
18
|
+
end
|
19
|
+
|
20
|
+
def number_of_transactions
|
21
|
+
@number_of_transactions ||= xml_data.xpath('NbOfTxs/text()').text
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
class Charges
|
5
|
+
|
6
|
+
attr_reader :xml_data
|
7
|
+
|
8
|
+
def initialize(xml_data)
|
9
|
+
@xml_data = xml_data
|
10
|
+
@total_charges_and_tax_amount = xml_data.xpath('TtlChrgsAndTaxAmt/text()').text
|
11
|
+
end
|
12
|
+
|
13
|
+
def total_charges_and_tax_amount
|
14
|
+
SepaFileParser::Misc.to_amount(@total_charges_and_tax_amount)
|
15
|
+
end
|
16
|
+
|
17
|
+
def total_charges_and_tax_amount_in_cents
|
18
|
+
SepaFileParser::Misc.to_amount_in_cents(@total_charges_and_tax_amount)
|
19
|
+
end
|
20
|
+
|
21
|
+
def records
|
22
|
+
@records ||= xml_data.xpath('Rcrd').map{ |x| SepaFileParser::Record.new(x) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
class Creditor
|
5
|
+
|
6
|
+
attr_reader :xml_data
|
7
|
+
|
8
|
+
def initialize(xml_data)
|
9
|
+
@xml_data = xml_data
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
@name ||= [
|
14
|
+
xml_data.xpath('RltdPties/Cdtr/Nm/text()').text,
|
15
|
+
xml_data.xpath('RltdPties/Cdtr/Pty/Nm/text()').text,
|
16
|
+
].reject(&:empty?).first.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def iban
|
20
|
+
@iban ||= xml_data.xpath('RltdPties/CdtrAcct/Id/IBAN/text()').text
|
21
|
+
end
|
22
|
+
|
23
|
+
def bic
|
24
|
+
@bic ||= [
|
25
|
+
xml_data.xpath('RltdAgts/CdtrAgt/FinInstnId/BIC/text()').text,
|
26
|
+
xml_data.xpath('RltdAgts/CdtrAgt/FinInstnId/BICFI/text()').text,
|
27
|
+
].reject(&:empty?).first.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def bank_name
|
31
|
+
@bank_name ||= xml_data.xpath('RltdAgts/CdtrAgt/FinInstnId/Nm/text()').text
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [SepaFileParser::PostalAddress, nil]
|
35
|
+
def postal_address # May be missing
|
36
|
+
postal_address = [
|
37
|
+
xml_data.xpath('RltdPties/Cdtr/PstlAdr'),
|
38
|
+
xml_data.xpath('RltdPties/Cdtr/Pty/PstlAdr'),
|
39
|
+
].reject(&:empty?).first
|
40
|
+
|
41
|
+
return nil if postal_address == nil || postal_address.empty?
|
42
|
+
|
43
|
+
@address ||= SepaFileParser::PostalAddress.new(postal_address)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
class Debitor
|
5
|
+
|
6
|
+
attr_reader :xml_data
|
7
|
+
|
8
|
+
def initialize(xml_data)
|
9
|
+
@xml_data = xml_data
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
@name ||= [
|
14
|
+
xml_data.xpath('RltdPties/Dbtr/Nm/text()').text,
|
15
|
+
xml_data.xpath('RltdPties/Dbtr/Pty/Nm/text()').text,
|
16
|
+
].reject(&:empty?).first.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def iban
|
20
|
+
@iban ||= xml_data.xpath('RltdPties/DbtrAcct/Id/IBAN/text()').text
|
21
|
+
end
|
22
|
+
|
23
|
+
def bic
|
24
|
+
@bic ||= [
|
25
|
+
xml_data.xpath('RltdAgts/DbtrAgt/FinInstnId/BIC/text()').text,
|
26
|
+
xml_data.xpath('RltdAgts/DbtrAgt/FinInstnId/BICFI/text()').text,
|
27
|
+
].reject(&:empty?).first.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def bank_name
|
31
|
+
@bank_name ||= xml_data.xpath('RltdAgts/DbtrAgt/FinInstnId/Nm/text()').text
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [SepaFileParser::PostalAddress, nil]
|
35
|
+
def postal_address # May be missing
|
36
|
+
postal_address = [
|
37
|
+
xml_data.xpath('RltdPties/Dbtr/PstlAdr'),
|
38
|
+
xml_data.xpath('RltdPties/Dbtr/Pty/PstlAdr'),
|
39
|
+
].reject(&:empty?).first
|
40
|
+
|
41
|
+
return nil if postal_address == nil || postal_address.empty?
|
42
|
+
|
43
|
+
@address ||= SepaFileParser::PostalAddress.new(postal_address)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SepaFileParser
|
4
|
+
class Entry
|
5
|
+
|
6
|
+
attr_reader :xml_data
|
7
|
+
|
8
|
+
def initialize(xml_data)
|
9
|
+
@xml_data = xml_data
|
10
|
+
@amount = xml_data.xpath('Amt/text()').text
|
11
|
+
end
|
12
|
+
|
13
|
+
def amount
|
14
|
+
SepaFileParser::Misc.to_amount(@amount)
|
15
|
+
end
|
16
|
+
|
17
|
+
def amount_in_cents
|
18
|
+
SepaFileParser::Misc.to_amount_in_cents(@amount)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [String]
|
22
|
+
def currency
|
23
|
+
@currency ||= xml_data.xpath('Amt/@Ccy').text
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Boolean]
|
27
|
+
def debit
|
28
|
+
@debit ||= xml_data.xpath('CdtDbtInd/text()').text.upcase == 'DBIT'
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Date]
|
32
|
+
def value_date
|
33
|
+
@value_date ||= ((date = xml_data.xpath('ValDt/Dt/text()').text).empty? ? nil : Date.parse(date))
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Date]
|
37
|
+
def booking_date
|
38
|
+
@booking_date ||= ((date = xml_data.xpath('BookgDt/Dt/text()').text).empty? ? nil : Date.parse(date))
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [DateTime]
|
42
|
+
def value_datetime
|
43
|
+
@value_datetime ||= ((datetime = xml_data.xpath('ValDt/DtTm/text()').text).empty? ? nil : DateTime.parse(datetime))
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [DateTime]
|
47
|
+
def booking_datetime
|
48
|
+
@booking_datetime ||= ((datetime = xml_data.xpath('BookgDt/DtTm/text()').text).empty? ? nil : DateTime.parse(datetime))
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String]
|
52
|
+
def bank_reference # May be missing
|
53
|
+
@bank_reference ||= xml_data.xpath('AcctSvcrRef/text()').text
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Array<SepaFileParser::Transaction>]
|
57
|
+
def transactions
|
58
|
+
@transactions ||= parse_transactions
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Boolean]
|
62
|
+
def credit?
|
63
|
+
!debit
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Boolean]
|
67
|
+
def debit?
|
68
|
+
debit
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Integer] either 1 or -1
|
72
|
+
def sign
|
73
|
+
credit? ? 1 : -1
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [Boolean]
|
77
|
+
def reversal?
|
78
|
+
@reversal ||= xml_data.xpath('RvslInd/text()').text.downcase == 'true'
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [Boolean]
|
82
|
+
def booked?
|
83
|
+
@booked ||= xml_data.xpath('Sts/text()').text.upcase == 'BOOK'
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return [String]
|
87
|
+
def additional_information
|
88
|
+
@additional_information ||= xml_data.xpath('AddtlNtryInf/text()').text
|
89
|
+
end
|
90
|
+
alias_method :description, :additional_information
|
91
|
+
|
92
|
+
# @return [SepaFileParser::Charges]
|
93
|
+
def charges
|
94
|
+
@charges ||= SepaFileParser::Charges.new(xml_data.xpath('Chrgs'))
|
95
|
+
end
|
96
|
+
# @return [SepaFileParser::BatchDetail, nil]
|
97
|
+
def batch_detail
|
98
|
+
@batch_detail ||= xml_data.xpath('NtryDtls/Btch').empty? ? nil : SepaFileParser::BatchDetail.new(@xml_data.xpath('NtryDtls/Btch'))
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def parse_transactions
|
104
|
+
transaction_details = xml_data.xpath('NtryDtls/TxDtls')
|
105
|
+
|
106
|
+
amt = nil
|
107
|
+
ccy = nil
|
108
|
+
|
109
|
+
if transaction_details.length == 1
|
110
|
+
amt = xml_data.xpath('Amt/text()').text
|
111
|
+
ccy = xml_data.xpath('Amt/@Ccy').text
|
112
|
+
end
|
113
|
+
|
114
|
+
xml_data.xpath('NtryDtls/TxDtls').map { |x| Transaction.new(x, debit?, amt, ccy) }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|