mirror of
https://github.com/OCA/bank-statement-import.git
synced 2025-01-20 12:37:43 +02:00
correctly parse entries with multiple transactions
Our bank does it. Care has been taken not to break cases where Ntry/NtryDtls/TxDtls isn't used: if any piece of information isn't found on these nodes, the matching information from the Ntry is used. Most often the entry has a sum or a summary and the transaction details are more precise and specific, so it makes sense to use their contents rather than its.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Class to parse camt files."""
|
"""Class to parse camt files."""
|
||||||
# © 2013-2016 Therp BV <http://therp.nl>
|
# © 2013-2016 Therp BV <http://therp.nl>
|
||||||
|
# Copyright 2017 Open Net Sàrl
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
import re
|
import re
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
@@ -46,7 +47,7 @@ class CamtParser(models.AbstractModel):
|
|||||||
break
|
break
|
||||||
|
|
||||||
def parse_transaction_details(self, ns, node, transaction):
|
def parse_transaction_details(self, ns, node, transaction):
|
||||||
"""Parse transaction details (message, party, account...)."""
|
"""Parse TxDtls node."""
|
||||||
# message
|
# message
|
||||||
self.add_value_from_node(
|
self.add_value_from_node(
|
||||||
ns, node, [
|
ns, node, [
|
||||||
@@ -67,6 +68,9 @@ class CamtParser(models.AbstractModel):
|
|||||||
],
|
],
|
||||||
transaction, 'ref'
|
transaction, 'ref'
|
||||||
)
|
)
|
||||||
|
amount = self.parse_amount(ns, node)
|
||||||
|
if amount != 0.0:
|
||||||
|
transaction['amount'] = amount
|
||||||
# remote party values
|
# remote party values
|
||||||
party_type = 'Dbtr'
|
party_type = 'Dbtr'
|
||||||
party_type_node = node.xpath(
|
party_type_node = node.xpath(
|
||||||
@@ -107,10 +111,11 @@ class CamtParser(models.AbstractModel):
|
|||||||
ns, account_node[0], './ns:Othr/ns:Id', transaction,
|
ns, account_node[0], './ns:Othr/ns:Id', transaction,
|
||||||
'account_number'
|
'account_number'
|
||||||
)
|
)
|
||||||
|
transaction['data'] = etree.tostring(node)
|
||||||
|
|
||||||
def parse_transaction(self, ns, node):
|
def parse_entry(self, ns, node):
|
||||||
"""Parse transaction (entry) node."""
|
"""Parse an Ntry node and yield transactions"""
|
||||||
transaction = {}
|
transaction = {'name': '/', 'amount': 0} # fallback defaults
|
||||||
self.add_value_from_node(
|
self.add_value_from_node(
|
||||||
ns, node, './ns:BkTxCd/ns:Prtry/ns:Cd', transaction,
|
ns, node, './ns:BkTxCd/ns:Prtry/ns:Cd', transaction,
|
||||||
'transfer_type'
|
'transfer_type'
|
||||||
@@ -121,27 +126,26 @@ class CamtParser(models.AbstractModel):
|
|||||||
ns, node, './ns:BookgDt/ns:Dt', transaction, 'execution_date')
|
ns, node, './ns:BookgDt/ns:Dt', transaction, 'execution_date')
|
||||||
self.add_value_from_node(
|
self.add_value_from_node(
|
||||||
ns, node, './ns:ValDt/ns:Dt', transaction, 'value_date')
|
ns, node, './ns:ValDt/ns:Dt', transaction, 'value_date')
|
||||||
|
amount = self.parse_amount(ns, node)
|
||||||
|
if amount != 0.0:
|
||||||
|
transaction['amount'] = amount
|
||||||
|
self.add_value_from_node(
|
||||||
|
ns, node, './ns:AddtlNtryInf', transaction, 'name')
|
||||||
|
self.add_value_from_node(
|
||||||
|
ns, node, [
|
||||||
|
'./ns:NtryDtls/ns:RmtInf/ns:Strd/ns:CdtrRefInf/ns:Ref',
|
||||||
|
'./ns:NtryDtls/ns:Btch/ns:PmtInfId',
|
||||||
|
],
|
||||||
|
transaction, 'ref'
|
||||||
|
)
|
||||||
|
|
||||||
transaction['amount'] = self.parse_amount(ns, node)
|
details_nodes = node.xpath(
|
||||||
|
|
||||||
details_node = node.xpath(
|
|
||||||
'./ns:NtryDtls/ns:TxDtls', namespaces={'ns': ns})
|
'./ns:NtryDtls/ns:TxDtls', namespaces={'ns': ns})
|
||||||
if details_node:
|
transaction_base = transaction
|
||||||
self.parse_transaction_details(ns, details_node[0], transaction)
|
for node in details_nodes:
|
||||||
if not transaction.get('name'):
|
transaction = transaction_base.copy()
|
||||||
self.add_value_from_node(
|
self.parse_transaction_details(ns, node, transaction)
|
||||||
ns, node, './ns:AddtlNtryInf', transaction, 'name')
|
yield transaction
|
||||||
if not transaction.get('name'):
|
|
||||||
transaction['name'] = '/'
|
|
||||||
if not transaction.get('ref'):
|
|
||||||
self.add_value_from_node(
|
|
||||||
ns, node, [
|
|
||||||
'./ns:NtryDtls/ns:Btch/ns:PmtInfId',
|
|
||||||
],
|
|
||||||
transaction, 'ref'
|
|
||||||
)
|
|
||||||
transaction['data'] = etree.tostring(node)
|
|
||||||
return transaction
|
|
||||||
|
|
||||||
def get_balance_amounts(self, ns, node):
|
def get_balance_amounts(self, ns, node):
|
||||||
"""Return opening and closing balance.
|
"""Return opening and closing balance.
|
||||||
@@ -193,12 +197,11 @@ class CamtParser(models.AbstractModel):
|
|||||||
ns, node, './ns:Acct/ns:Ccy', result, 'currency')
|
ns, node, './ns:Acct/ns:Ccy', result, 'currency')
|
||||||
result['balance_start'], result['balance_end_real'] = (
|
result['balance_start'], result['balance_end_real'] = (
|
||||||
self.get_balance_amounts(ns, node))
|
self.get_balance_amounts(ns, node))
|
||||||
transaction_nodes = node.xpath('./ns:Ntry', namespaces={'ns': ns})
|
entry_nodes = node.xpath('./ns:Ntry', namespaces={'ns': ns})
|
||||||
result['transactions'] = []
|
transactions = []
|
||||||
for entry_node in transaction_nodes:
|
for entry_node in entry_nodes:
|
||||||
transaction = self.parse_transaction(ns, entry_node)
|
transactions.extend(self.parse_entry(ns, entry_node))
|
||||||
if transaction:
|
result['transactions'] = transactions
|
||||||
result['transactions'].append(transaction)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def check_version(self, ns, root):
|
def check_version(self, ns, root):
|
||||||
|
|||||||
Reference in New Issue
Block a user