Browse Source

[IMP] re-add enhanced balance determination.

pull/153/head
Ronald Portier 6 years ago
parent
commit
702cf82ccd
  1. 132
      account_bank_statement_import_camt/models/parser.py

132
account_bank_statement_import_camt/models/parser.py

@ -1,19 +1,19 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2013-2018 Therp BV <http://therp.nl>
# Copyright 2017 Open Net Sàrl
# Copyright 2015 1200wd.com
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# Copyright 2013-2018 Therp BV <https://therp.nl>.
# Copyright 2015 1200wd.com.
# Copyright 2017 Open Net Sàrl.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging import logging
import re import re
from copy import copy
from datetime import datetime from datetime import datetime
from lxml import etree from lxml import etree
from openerp import _
from openerp import _, models
from openerp.addons.account_bank_statement_import.parserlib import ( from openerp.addons.account_bank_statement_import.parserlib import (
BankStatement) BankStatement)
from openerp import models
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -30,21 +30,29 @@ class CamtParser(models.AbstractModel):
return node.xpath(expr, namespaces={'ns': self.namespace}) return node.xpath(expr, namespaces={'ns': self.namespace})
def parse_amount(self, node): def parse_amount(self, node):
"""Parse element that contains Amount and CreditDebitIndicator."""
"""Parse element that contains Amount and CreditDebitIndicator.
First try to get debit/credit and amount from transaction details.
If not possible, try to get them from ancester element entry.
Function is also called for statement opening balance!
Therefore also search for amount anywhere in node, if not found
elsewhere.
"""
if node is None: if node is None:
return 0.0 return 0.0
sign = 1 sign = 1
amount = 0.0 amount = 0.0
sign_node = node.xpath('ns:CdtDbtInd', namespaces={'ns': ns})
sign_node = self.xpath(node, '../../ns:CdtDbtInd')
if not sign_node: if not sign_node:
sign_node = node.xpath(
'../../ns:CdtDbtInd', namespaces={'ns': ns})
sign_node = self.xpath(node, 'ns:CdtDbtInd')
if sign_node and sign_node[0].text == 'DBIT': if sign_node and sign_node[0].text == 'DBIT':
sign = -1 sign = -1
amount_node = node.xpath('ns:Amt', namespaces={'ns': ns})
amount_node = self.xpath(node, './ns:AmtDtls/ns:TxAmt/ns:Amt')
if not amount_node:
amount_node = self.xpath(node, '../../ns:Amt')
if not amount_node: if not amount_node:
amount_node = node.xpath(
'./ns:AmtDtls/ns:TxAmt/ns:Amt', namespaces={'ns': ns})
amount_node = self.xpath(node, 'ns:Amt')
if amount_node: if amount_node:
amount = sign * float(amount_node[0].text) amount = sign * float(amount_node[0].text)
return amount return amount
@ -73,11 +81,10 @@ class CamtParser(models.AbstractModel):
setattr(obj, attr_name, default) setattr(obj, attr_name, default)
def parse_transaction_details(self, node, transaction): def parse_transaction_details(self, node, transaction):
"""Parse TxDtls node."""
"""Parse transaction details (message, party, account...)."""
# message # message
self.add_value_from_node( self.add_value_from_node(
node,
[
node, [
'./ns:RmtInf/ns:Ustrd', './ns:RmtInf/ns:Ustrd',
'./ns:AddtlTxInf', './ns:AddtlTxInf',
'./ns:AddtlNtryInf', './ns:AddtlNtryInf',
@ -94,8 +101,7 @@ class CamtParser(models.AbstractModel):
'./ns:RmtInf/ns:Strd/ns:CdtrRefInf/ns:Ref', './ns:RmtInf/ns:Strd/ns:CdtrRefInf/ns:Ref',
'./ns:Refs/ns:EndToEndId', './ns:Refs/ns:EndToEndId',
], ],
transaction, 'eref'
)
transaction, 'eref')
amount = self.parse_amount(node) amount = self.parse_amount(node)
if amount != 0.0: if amount != 0.0:
transaction['amount'] = amount transaction['amount'] = amount
@ -107,21 +113,17 @@ class CamtParser(models.AbstractModel):
party_node = self.xpath(node, './ns:RltdPties/ns:%s' % party_type) party_node = self.xpath(node, './ns:RltdPties/ns:%s' % party_type)
if party_node: if party_node:
self.add_value_from_node( self.add_value_from_node(
party_node[0], './ns:Nm', transaction, 'remote_owner'
)
party_node[0], './ns:Nm', transaction, 'remote_owner')
self.add_value_from_node( self.add_value_from_node(
party_node[0], './ns:PstlAdr/ns:Ctry', transaction, party_node[0], './ns:PstlAdr/ns:Ctry', transaction,
'remote_owner_country'
)
'remote_owner_country')
address_node = self.xpath( address_node = self.xpath(
party_node[0], './ns:PstlAdr/ns:AdrLine'
)
party_node[0], './ns:PstlAdr/ns:AdrLine')
if address_node: if address_node:
transaction.remote_owner_address = [address_node[0].text] transaction.remote_owner_address = [address_node[0].text]
# Get remote_account from iban or from domestic account: # Get remote_account from iban or from domestic account:
account_node = self.xpath( account_node = self.xpath(
node, './ns:RltdPties/ns:%sAcct/ns:Id' % party_type
)
node, './ns:RltdPties/ns:%sAcct/ns:Id' % party_type)
if account_node: if account_node:
iban_node = self.xpath(account_node[0], './ns:IBAN') iban_node = self.xpath(account_node[0], './ns:IBAN')
if iban_node: if iban_node:
@ -129,62 +131,56 @@ class CamtParser(models.AbstractModel):
bic_node = self.xpath( bic_node = self.xpath(
node, node,
'./ns:RltdAgts/ns:%sAgt/ns:FinInstnId/ns:BIC' './ns:RltdAgts/ns:%sAgt/ns:FinInstnId/ns:BIC'
% party_type
)
% party_type)
if bic_node: if bic_node:
transaction.remote_bank_bic = bic_node[0].text transaction.remote_bank_bic = bic_node[0].text
else: else:
self.add_value_from_node( self.add_value_from_node(
account_node[0], './ns:Othr/ns:Id', transaction, account_node[0], './ns:Othr/ns:Id', transaction,
'remote_account'
)
'remote_account')
def parse_transaction(self, node, transaction):
"""Parse transaction (entry) node."""
self.add_value_from_node(
node, './ns:BkTxCd/ns:Prtry/ns:Cd', transaction,
'transfer_type'
)
self.add_value_from_node(
node, './ns:BookgDt/ns:Dt', transaction, 'date')
self.add_value_from_node(
node, './ns:BookgDt/ns:Dt', transaction, 'execution_date')
self.add_value_from_node(
node, './ns:ValDt/ns:Dt', transaction, 'value_date')
transaction.transferred_amount = self.parse_amount(node)
def default_transaction_data(self, node, transaction):
if not transaction.eref:
batch_node = self.xpath(node, './ns:NtryDtls/ns:Btch') batch_node = self.xpath(node, './ns:NtryDtls/ns:Btch')
if batch_node: if batch_node:
self.add_value_from_node( self.add_value_from_node(
batch_node[0], './ns:PmtInfId', transaction, 'eref') batch_node[0], './ns:PmtInfId', transaction, 'eref')
else: else:
self.add_value_from_node(
node, './ns:AddtlNtryInf', transaction, 'name')
self.add_value_from_node( self.add_value_from_node(
node, node,
['./ns:NtryDtls/ns:RmtInf/ns:Strd/ns:CdtrRefInf/ns:Ref', ['./ns:NtryDtls/ns:RmtInf/ns:Strd/ns:CdtrRefInf/ns:Ref',
'./ns:NtryDtls/ns:Btch/ns:PmtInfId'], './ns:NtryDtls/ns:Btch/ns:PmtInfId'],
transaction, transaction,
'eref') 'eref')
details_nodes = node.xpath(
'./ns:NtryDtls/ns:TxDtls', namespaces={'ns': ns})
if len(details_nodes) == 0:
if not transaction.message:
self.add_value_from_node(
node, './ns:AddtlNtryInf', transaction, 'message')
def parse_entry(self, node, transaction):
"""Parse transaction (entry) node."""
self.add_value_from_node(
node, './ns:BkTxCd/ns:Prtry/ns:Cd', transaction, 'transfer_type')
self.add_value_from_node(
node, './ns:BookgDt/ns:Dt', transaction, 'date')
self.add_value_from_node(
node, './ns:BookgDt/ns:Dt', transaction, 'execution_date')
self.add_value_from_node(
node, './ns:ValDt/ns:Dt', transaction, 'value_date')
transaction.transferred_amount = self.parse_amount(node)
self.add_value_from_node(
node, './ns:AddtlNtryInf', transaction, 'name')
detail_nodes = self.xpath(node, './ns:NtryDtls/ns:TxDtls')
if len(detail_nodes) == 0:
self.default_transaction_data(node, transaction)
transaction.data = etree.tostring(node) transaction.data = etree.tostring(node)
yield transaction yield transaction
return return
transaction_base = transaction transaction_base = transaction
for i, dnode in enumerate(details_nodes):
for i, dnode in enumerate(detail_nodes):
transaction = copy(transaction_base) transaction = copy(transaction_base)
self.parse_transaction_details(ns, dnode, transaction)
# transactions['data'] should be a synthetic xml snippet which
# contains only the TxDtls that's relevant.
# only set this field if statement lines have it
if 'data' in self.pool['account.bank.statement.line']._fields:
data = copy(node)
for j, dnode in enumerate(data.xpath(
'./ns:NtryDtls/ns:TxDtls', namespaces={'ns': ns})):
if j != i:
dnode.getparent().remove(dnode)
transaction['data'] = etree.tostring(data)
self.parse_transaction_details(dnode, transaction)
self.default_transaction_data(node, transaction)
transaction.data = etree.tostring(dnode)
yield transaction yield transaction
def get_balance_type_node(self, node, balance_type): def get_balance_type_node(self, node, balance_type):
@ -249,13 +245,16 @@ class CamtParser(models.AbstractModel):
node, './ns:Acct/ns:Ccy', statement, 'local_currency') node, './ns:Acct/ns:Ccy', statement, 'local_currency')
statement.start_balance = self.get_start_balance(node) statement.start_balance = self.get_start_balance(node)
statement.end_balance = self.get_end_balance(node) statement.end_balance = self.get_end_balance(node)
transaction_nodes = self.xpath(node, './ns:Ntry')
entry_nodes = self.xpath(node, './ns:Ntry')
total_amount = 0 total_amount = 0
for entry_node in transaction_nodes:
transactions = []
for entry_node in entry_nodes:
# There might be multiple transactions in one entry!!
transaction = statement.create_transaction() transaction = statement.create_transaction()
transaction.data = etree.tostring(entry_node)
self.parse_transaction(entry_node, transaction)
transactions.extend(self.parse_entry(entry_node, transaction))
for transaction in transactions:
total_amount += transaction.transferred_amount total_amount += transaction.transferred_amount
statement['transactions'] = transactions
if statement['transactions']: if statement['transactions']:
execution_date = statement['transactions'][0].execution_date[:10] execution_date = statement['transactions'][0].execution_date[:10]
statement.date = datetime.strptime(execution_date, "%Y-%m-%d") statement.date = datetime.strptime(execution_date, "%Y-%m-%d")
@ -270,8 +269,7 @@ class CamtParser(models.AbstractModel):
" Total amount %s."), " Total amount %s."),
statement.start_balance, statement.start_balance,
statement.end_balance, statement.end_balance,
total_amount
)
total_amount)
return statement return statement
def check_version(self, root): def check_version(self, root):

Loading…
Cancel
Save