Class: Camt::Parser

Inherits:
Object show all
Defined in:
lib/camt/parser.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#fileObject

Returns the value of attribute file.



4
5
6
# File 'lib/camt/parser.rb', line 4

def file
  @file
end

Instance Method Details

#get_balance_type_node(node, balance_type) ⇒ Object



6
7
8
9
10
# File 'lib/camt/parser.rb', line 6

def get_balance_type_node node, balance_type
  # :param node: BkToCstmrStmt/Stmt/Bal node
  # :param balance type: one of 'OPBD', 'PRCD', 'ITBD', 'CLBD'
  return node.at("./Bal/Tp/CdOrPrtry/Cd[text()='#{balance_type}']/../../..")
end

#get_end_balance(node) ⇒ Object



34
35
36
37
38
39
40
41
42
43
# File 'lib/camt/parser.rb', line 34

def get_end_balance(node)
  # Find the (only) balance node with code ClosingBalance, or
  # the second (and last) balance node with code InterimBalance in
  # the case of continued pagination.
  #
  # :param node: BkToCstmrStmt/Stmt/Bal node
  balance_type_node = nil
  ['CLBD', 'ITBD'].detect{|code| balance_type_node = get_balance_type_node(node, code) }
  parse_amount balance_type_node
end

#get_party_values(node) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/camt/parser.rb', line 86

def get_party_values node
  # Determine to get either the debtor or creditor party node
  # and extract the available data from it
  values = {}

  party_type = node.at('../../CdtDbtInd').text == 'CRDT' ? 'Dbtr' : 'Cdtr'

  party_node = node.at("./RltdPties/#{party_type}")
   = node.at("./RltdPties/#{party_type}Acct/Id")
  bic_node = node.at("./RltdAgts/#{party_type}Agt/FinInstnId/BIC")

  if party_node
    values[:remote_owner] = party_node.at('./Nm').try(:text)
    values[:remote_owner_country] = party_node.at('./PstlAdr/Ctry').try(:text)
    values[:remote_owner_address] = party_node.at('./PstlAdr/AdrLine').try(:text)
  end

  if 
    values[:remote_account] = .at('./IBAN').try(:text)
    values[:remote_account] ||= .at('./Othr/Id').try(:text)
    values[:remote_bank_bic] = bic_node.text if bic_node
  end

  return values
end

#get_start_balance(node) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/camt/parser.rb', line 22

def get_start_balance(node)
  # Find the (only) balance node with code OpeningBalance, or
  # the only one with code 'PreviousClosingBalance'
  # or the first balance node with code InterimBalance in
  # the case of preceeding pagination.
  #
  # :param node: BkToCstmrStmt/Stmt/Bal node
  balance_type_node = nil
  ['OPBD', 'PRCD', 'ITBD'].detect{|code| balance_type_node = get_balance_type_node(node, code) }
  parse_amount balance_type_node
end

#get_transfer_type(node) ⇒ Object



67
68
69
70
71
# File 'lib/camt/parser.rb', line 67

def get_transfer_type node
  # Map properietary codes from BkTxCd/Prtry/Cd.
  # :param node: BkTxCd/Prtry node
  { proprietary_code: node.at('./Cd').text, proprietary_issuer: node.at('./Issr').text } if node
end

#parse(file) ⇒ Object



167
168
169
170
171
# File 'lib/camt/parser.rb', line 167

def parse file
  self.file = file
  file.doc.remove_namespaces!
  file.doc.xpath('//BkToCstmrStmt').map{|node| parse_message node }
end

#parse_amount(node) ⇒ Object



12
13
14
15
16
17
18
19
20
# File 'lib/camt/parser.rb', line 12

def parse_amount(node)
  # Parse an element that contains both Amount and CreditDebitIndicator
  #
  # :return: signed amount
  # :returntype: float

  sign = node.at('./CdtDbtInd').text == 'DBIT' ? -1 : 1
  return sign * node.at('./Amt').text.to_f
end

#parse_message(node) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/camt/parser.rb', line 138

def parse_message node
  group_header_node = node.at('./GrpHdr')

  group_header = GroupHeader.new
  group_header.message_id       = group_header_node.at('./MsgId').text
  group_header.created_at       = Time.parse(group_header_node.at('./CreDtTm').text)
  group_header.additional_info  = group_header_node.at('./AddtlInf').try(:text)

  if recipient_node = group_header_node.at('./MsgRcpt')
    group_header.recipient = {
      name: recipient_node.at('./Nm').try(:text),
      postal_address: recipient_node.at('./PstlAdr').try(:text),
      identification: recipient_node.at('./Id').try(:text),
      country_of_residence: recipient_node.at('./CtryOfRes').try(:text),
      contact_details: recipient_node.at('./CtctDtls').try(:text)
    }
  end

  if pagination_node = group_header_node.at('./MsgPgntn')
    group_header.pagination = { page: pagination_node.at('./PgNb').text, last_page: (pagination_node.at('./LastPgInd').text == 'true') }
  end

  message = Message.new
  message.group_header = group_header
  message.statements = node.xpath('./Stmt').map{|node| parse_Stmt node }

  message
end

#parse_Ntry(node) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/camt/parser.rb', line 73

def parse_Ntry node
  # :param node: Ntry node

  transaction = Transaction.new
  transaction.execution_date      = node.at('./BookgDt/Dt | ./BookgDt/DtTm').text
  transaction.effective_date      = node.at('./ValDt/Dt | ./ValDt/DtTm').text
  transaction.transfer_type       = get_transfer_type(node.at('./BkTxCd/Prtry'))
  transaction.transferred_amount  = parse_amount(node)
  transaction.transaction_details = node.xpath('.//NtryDtls//TxDtls').map{ |node| parse_TxDtls(node) }

  transaction
end

#parse_Stmt(node) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/camt/parser.rb', line 45

def parse_Stmt node
  # Parse a single Stmt node.
  #
  # Be sure to craft a unique, but short enough statement identifier,
  # as it is used as the basis of the generated move lines' names
  # which overflow when using the full IBAN and CAMT statement id.

  statement = Statement.new

  statement.id = node.at('./Id').text
  statement.electronic_sequence_number = node.at('./ElctrncSeqNb').text
  statement.created_at = Time.parse(node.at('./CreDtTm').text)
  statement. = node.at('./Acct/Id').text.strip
  statement.local_currency = node.at('./Acct/Ccy').text
  statement.date = Time.parse(node.at('./Ntry[1]/ValDt/Dt | ./Ntry[1]/ValDt/DtTm').text)
  statement.start_balance = get_start_balance(node)
  statement.end_balance = get_end_balance(node)
  statement.transactions = node.xpath('./Ntry').map{ |node| parse_Ntry(node) }

  statement
end

#parse_TxDtls(node) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/camt/parser.rb', line 112

def parse_TxDtls node
  # Parse a single TxDtls node
  transaction_details = {}

  if (unstructured = node.xpath('./RmtInf/Ustrd')).any?
    transaction_details[:messages] = unstructured.map(&:text)
  end

  if (structured = node.xpath('./RmtInf/Strd/CdtrRefInf/Ref | ./Refs/EndToEndId')).any?
    transaction_details[:references] = structured.map(&:text)
  end

  if mandate_identifier = node.at('./Refs/MndtId').try(:text)
    transaction_details[:mandate_identifier] = mandate_identifier
  end

  if reason = node.at('./RtrInf/Rsn/Cd').try(:text)
    reason_language = Camt::Reasons.keys.include?(file.country_code) ? file.country_code : 'EN'
    transaction_details[:reason] = { code: reason, description: Camt::Reasons[reason_language][reason] }
  end

  transaction_details[:party] = get_party_values node

  transaction_details
end