Browse Source

[IMP] - manage multi-bank-account statement import

refs: #209
pull/213/head
sbejaoui 6 years ago
parent
commit
c2cee48ad1
  1. 66
      account_bank_statement_import_camt_oca/models/account_bank_statement_import.py
  2. 41
      account_bank_statement_import_camt_oca/models/parser.py
  3. 581
      account_bank_statement_import_camt_oca/test_files/test-camt053-more-than-one-xml
  4. 30
      account_bank_statement_import_camt_oca/tests/test_import_bank_statement.py

66
account_bank_statement_import_camt_oca/models/account_bank_statement_import.py

@ -4,6 +4,7 @@ import logging
from io import BytesIO
import zipfile
from odoo import api, models
from odoo.addons.base.models.res_bank import sanitize_account_number
_logger = logging.getLogger(__name__)
@ -11,23 +12,86 @@ _logger = logging.getLogger(__name__)
class AccountBankStatementImport(models.TransientModel):
_inherit = 'account.bank.statement.import'
@api.model
def _xml_split_file(self, data_file):
"""BNP France is known to merge xml files"""
if not data_file.startswith(b'<?xml'):
return [data_file]
data_file_elements = []
all_files = data_file.split(b'<?xml')
for file in all_files:
if file:
data_file_elements.append(b'<?xml' + file)
return data_file_elements
@api.model
def _parse_file(self, data_file):
"""Parse a CAMT053 XML file."""
try:
parser = self.env['account.bank.statement.import.camt.parser']
_logger.debug("Try parsing with camt.")
return parser.parse(data_file)
currency = None
account_number = None
transactions = []
data_file_elements = self._xml_split_file(data_file)
for data_file_element in data_file_elements:
currency, account_number, element_transactions = parser.parse(
data_file_element
)
transactions.extend(element_transactions)
return currency, account_number, transactions
except ValueError:
try:
with zipfile.ZipFile(BytesIO(data_file)) as data:
journal_account_number = None
journal_currency_name = None
if self.env.context.get('journal_id', None):
journal_id = self.env.context.get('journal_id', None)
journal = self.env['account.journal'].browse(
journal_id
)
journal_account_number = sanitize_account_number(
journal.bank_account_id.acc_number
)
journal_currency_name = journal.currency_id.name
currency = None
account_number = None
transactions = []
for member in data.namelist():
old_account_number = account_number
old_currency = currency
currency, account_number, new = self._parse_file(
data.open(member).read()
)
if (
journal_account_number
and journal_account_number != account_number
):
continue
if (
not journal_account_number
and old_account_number
and old_account_number != account_number
):
raise ValueError(
'File containing statements for multiple '
'accounts'
)
if (
journal_currency_name
and journal_currency_name != currency
):
continue
if (
not journal_currency_name
and old_currency
and old_currency != currency
):
raise ValueError(
'File containing statements for multiple '
'currencies'
)
transactions.extend(new)
return currency, account_number, transactions
except (zipfile.BadZipFile, ValueError):

41
account_bank_statement_import_camt_oca/models/parser.py

@ -6,6 +6,7 @@ import re
from lxml import etree
from odoo import models
from odoo.addons.base.models.res_bank import sanitize_account_number
class CamtParser(models.AbstractModel):
@ -222,6 +223,15 @@ class CamtParser(models.AbstractModel):
def parse(self, data):
"""Parse a camt.052 or camt.053 or camt.054 file."""
journal_account_number = None
journal_currency_name = None
if self.env.context.get('journal_id', None):
journal_id = self.env.context.get('journal_id', None)
journal = self.env['account.journal'].browse(journal_id)
journal_account_number = sanitize_account_number(
journal.bank_account_id.acc_number
)
journal_currency_name = journal.currency_id.name
try:
root = etree.fromstring(
data, parser=etree.XMLParser(recover=True))
@ -241,8 +251,37 @@ class CamtParser(models.AbstractModel):
statement = self.parse_statement(ns, node)
if len(statement['transactions']):
if 'currency' in statement:
old_currency = currency
currency = statement.pop('currency')
if (
journal_currency_name
and journal_currency_name != currency
):
continue
if (
not journal_currency_name
and old_currency
and old_currency != currency
):
raise ValueError(
'File containing statements for multiple '
'currencies'
)
if 'account_number' in statement:
old_account_number = account_number
account_number = statement.pop('account_number')
if (
journal_account_number
and journal_account_number != account_number
):
continue
if (
not journal_account_number
and old_account_number
and old_account_number != account_number
):
raise ValueError(
'File containing statements for multiple accounts'
)
statements.append(statement)
return currency, account_number, statements
return currency, journal_account_number or account_number, statements

581
account_bank_statement_import_camt_oca/test_files/test-camt053-more-than-one-xml

@ -0,0 +1,581 @@
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
<BkToCstmrStmt>
<GrpHdr>
<MsgId>TESTBANK/NL/1420561226673</MsgId>
<CreDtTm>2014-01-06T16:20:26.673Z</CreDtTm>
</GrpHdr>
<Stmt>
<Id>1234Test/1</Id>
<LglSeqNb>2</LglSeqNb>
<CreDtTm>2014-01-06T16:20:26.673Z</CreDtTm>
<FrToDt>
<FrDtTm>2014-01-05T00:00:00.000Z</FrDtTm>
<ToDtTm>2014-01-05T23:59:59.999Z</ToDtTm>
</FrToDt>
<Acct>
<Id>
<IBAN>NL77ABNA0574908765</IBAN>
</Id>
<Nm>Example company</Nm>
<Svcr>
<FinInstnId>
<BIC>ABNANL2A</BIC>
</FinInstnId>
</Svcr>
</Acct>
<Bal>
<Tp>
<CdOrPrtry>
<Cd>OPBD</Cd>
</CdOrPrtry>
</Tp>
<Amt Ccy="EUR">15568.27</Amt>
<CdtDbtInd>CRDT</CdtDbtInd>
<Dt>
<Dt>2014-01-05</Dt>
</Dt>
</Bal>
<Bal>
<Tp>
<CdOrPrtry>
<Cd>CLBD</Cd>
</CdOrPrtry>
</Tp>
<Amt Ccy="EUR">15121.12</Amt>
<CdtDbtInd>CRDT</CdtDbtInd>
<Dt>
<Dt>2014-01-05</Dt>
</Dt>
</Bal>
<Ntry>
<Amt Ccy="EUR">754.25</Amt>
<CdtDbtInd>DBIT</CdtDbtInd>
<Sts>BOOK</Sts>
<BookgDt>
<Dt>2014-01-05</Dt>
</BookgDt>
<ValDt>
<Dt>2014-01-05</Dt>
</ValDt>
<BkTxCd>
<Domn>
<Cd>PMNT</Cd>
<Fmly>
<Cd>RDDT</Cd>
<SubFmlyCd>ESDD</SubFmlyCd>
</Fmly>
</Domn>
<Prtry>
<Cd>EI</Cd>
</Prtry>
</BkTxCd>
<NtryDtls>
<TxDtls>
<Refs>
<InstrId>INNDNL2U20141231000142300002844</InstrId>
<EndToEndId>435005714488-ABNO33052620</EndToEndId>
<MndtId>1880000341866</MndtId>
</Refs>
<AmtDtls>
<TxAmt>
<Amt Ccy="EUR">754.25</Amt>
</TxAmt>
</AmtDtls>
<RltdPties>
<Cdtr>
<Nm>INSURANCE COMPANY TESTX</Nm>
<PstlAdr>
<StrtNm>TEST STREET 20</StrtNm>
<TwnNm>1234 AB TESTCITY</TwnNm>
<Ctry>NL</Ctry>
</PstlAdr>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>NL46ABNA0499998748</IBAN>
</Id>
</CdtrAcct>
</RltdPties>
<RltdAgts>
<CdtrAgt>
<FinInstnId>
<BIC>ABNANL2A</BIC>
</FinInstnId>
</CdtrAgt>
</RltdAgts>
<RmtInf>
<Ustrd>Insurance policy 857239PERIOD 01.01.2014 - 31.12.2014</Ustrd>
</RmtInf>
<AddtlTxInf>MKB Insurance 859239PERIOD 01.01.2014 - 31.12.2014</AddtlTxInf>
</TxDtls>
</NtryDtls>
</Ntry>
<Ntry>
<Amt Ccy="EUR">664.05</Amt>
<CdtDbtInd>DBIT</CdtDbtInd>
<RvslInd>true</RvslInd>
<Sts>BOOK</Sts>
<BookgDt>
<Dt>2014-01-05</Dt>
</BookgDt>
<ValDt>
<Dt>2014-01-05</Dt>
</ValDt>
<BkTxCd>
<Domn>
<Cd>PMNT</Cd>
<Fmly>
<Cd>IDDT</Cd>
<SubFmlyCd>UPDD</SubFmlyCd>
</Fmly>
</Domn>
<Prtry>
<Cd>EIST</Cd>
</Prtry>
</BkTxCd>
<NtryDtls>
<Btch>
<MsgId>2014/125</MsgId>
<PmtInfId>2018/125-20141229-NORM</PmtInfId>
<NbOfTxs>2</NbOfTxs>
<TtlAmt Ccy="EUR">664.05</TtlAmt>
<CdtDbtInd>DBIT</CdtDbtInd>
</Btch>
<TxDtls>
<Refs>
<InstrId>TESTBANK/NL/20141229/01206408</InstrId>
<EndToEndId>TESTBANK/NL/20141229/01206408</EndToEndId>
<MndtId>NL22ZZZ524885430000-C0125.1</MndtId>
</Refs>
<AmtDtls>
<TxAmt>
<Amt Ccy="EUR">564.05</Amt>
</TxAmt>
</AmtDtls>
<RltdPties>
<Cdtr>
<Nm>Test Customer</Nm>
<PstlAdr>
<Ctry>NL</Ctry>
</PstlAdr>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>NL46ABNA0499998748</IBAN>
</Id>
</CdtrAcct>
</RltdPties>
<RltdAgts>
<CdtrAgt>
<FinInstnId>
<BIC>ABNANL2A</BIC>
</FinInstnId>
</CdtrAgt>
</RltdAgts>
<RmtInf>
<Ustrd>Direct Debit S14 0410</Ustrd>
</RmtInf>
<RtrInf>
<Rsn>
<Cd>AC06</Cd>
</Rsn>
</RtrInf>
<AddtlTxInf>Direct debit S14 0410 AC07 Rek.nummer blokkade TESTBANK/NL/20141229/01206408</AddtlTxInf>
</TxDtls>
<TxDtls>
<Refs>
<InstrId>TESTBANK/NL/20141229/01206407</InstrId>
<EndToEndId>TESTBANK/NL/20141229/01206407</EndToEndId>
<MndtId>NL22ZZZ524885430000-C0125.2</MndtId>
</Refs>
<AmtDtls>
<TxAmt>
<Amt Ccy="EUR">100.00</Amt>
</TxAmt>
</AmtDtls>
<RltdPties>
<Cdtr>
<Nm>Test Customer</Nm>
<PstlAdr>
<Ctry>NL</Ctry>
</PstlAdr>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>NL46ABNA0499998748</IBAN>
</Id>
</CdtrAcct>
</RltdPties>
<RltdAgts>
<CdtrAgt>
<FinInstnId>
<BIC>ABNANL2A</BIC>
</FinInstnId>
</CdtrAgt>
</RltdAgts>
<RmtInf>
<Ustrd>Direct Debit S14 0410</Ustrd>
</RmtInf>
<RtrInf>
<Rsn>
<Cd>AC06</Cd>
</Rsn>
</RtrInf>
<AddtlTxInf>Direct debit S14 0410 AC07 Rek.nummer blokkade TESTBANK/NL/20141229/01206408</AddtlTxInf>
</TxDtls>
</NtryDtls>
</Ntry>
<Ntry>
<Amt Ccy="EUR">1405.31</Amt>
<CdtDbtInd>CRDT</CdtDbtInd>
<Sts>BOOK</Sts>
<BookgDt>
<Dt>2014-01-05</Dt>
</BookgDt>
<ValDt>
<Dt>2014-01-05</Dt>
</ValDt>
<BkTxCd>
<Domn>
<Cd>PMNT</Cd>
<Fmly>
<Cd>RCDT</Cd>
<SubFmlyCd>ESCT</SubFmlyCd>
</Fmly>
</Domn>
<Prtry>
<Cd>ET</Cd>
</Prtry>
</BkTxCd>
<NtryDtls>
<TxDtls>
<Refs>
<InstrId>INNDNL2U20140105000217200000708</InstrId>
<EndToEndId>115</EndToEndId>
</Refs>
<AmtDtls>
<TxAmt>
<Amt Ccy="EUR">1405.31</Amt>
</TxAmt>
</AmtDtls>
<RltdPties>
<Dbtr>
<Nm>3rd party Media</Nm>
<PstlAdr>
<StrtNm>SOMESTREET 570-A</StrtNm>
<TwnNm>1276 ML HOUSCITY</TwnNm>
<Ctry>NL</Ctry>
</PstlAdr>
</Dbtr>
<DbtrAcct>
<Id>
<IBAN>NL69ABNA0522123643</IBAN>
</Id>
</DbtrAcct>
</RltdPties>
<RltdAgts>
<DbtrAgt>
<FinInstnId>
<BIC>ABNANL2A</BIC>
</FinInstnId>
</DbtrAgt>
</RltdAgts>
<AddtlTxInf>#RD PARTY MEDIA CUSNO 90782 4210773</AddtlTxInf>
</TxDtls>
</NtryDtls>
</Ntry>
</Stmt>
</BkToCstmrStmt>
</Document>
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
<BkToCstmrStmt>
<GrpHdr>
<MsgId>TESTBANK/NL/1420561226673</MsgId>
<CreDtTm>2014-01-06T16:20:26.673Z</CreDtTm>
</GrpHdr>
<Stmt>
<Id>1234Test/1</Id>
<LglSeqNb>2</LglSeqNb>
<CreDtTm>2014-01-06T16:20:26.673Z</CreDtTm>
<FrToDt>
<FrDtTm>2014-01-05T00:00:00.000Z</FrDtTm>
<ToDtTm>2014-01-05T23:59:59.999Z</ToDtTm>
</FrToDt>
<Acct>
<Id>
<IBAN>NL77ABNA0574908765</IBAN>
</Id>
<Nm>Example company</Nm>
<Svcr>
<FinInstnId>
<BIC>ABNANL2A</BIC>
</FinInstnId>
</Svcr>
</Acct>
<Bal>
<Tp>
<CdOrPrtry>
<Cd>OPBD</Cd>
</CdOrPrtry>
</Tp>
<Amt Ccy="EUR">15568.27</Amt>
<CdtDbtInd>CRDT</CdtDbtInd>
<Dt>
<Dt>2014-01-05</Dt>
</Dt>
</Bal>
<Bal>
<Tp>
<CdOrPrtry>
<Cd>CLBD</Cd>
</CdOrPrtry>
</Tp>
<Amt Ccy="EUR">15121.12</Amt>
<CdtDbtInd>CRDT</CdtDbtInd>
<Dt>
<Dt>2014-01-05</Dt>
</Dt>
</Bal>
<Ntry>
<Amt Ccy="EUR">754.25</Amt>
<CdtDbtInd>DBIT</CdtDbtInd>
<Sts>BOOK</Sts>
<BookgDt>
<Dt>2014-01-05</Dt>
</BookgDt>
<ValDt>
<Dt>2014-01-05</Dt>
</ValDt>
<BkTxCd>
<Domn>
<Cd>PMNT</Cd>
<Fmly>
<Cd>RDDT</Cd>
<SubFmlyCd>ESDD</SubFmlyCd>
</Fmly>
</Domn>
<Prtry>
<Cd>EI</Cd>
</Prtry>
</BkTxCd>
<NtryDtls>
<TxDtls>
<Refs>
<InstrId>INNDNL2U20141231000142300002844</InstrId>
<EndToEndId>435005714488-ABNO33052620</EndToEndId>
<MndtId>1880000341866</MndtId>
</Refs>
<AmtDtls>
<TxAmt>
<Amt Ccy="EUR">754.25</Amt>
</TxAmt>
</AmtDtls>
<RltdPties>
<Cdtr>
<Nm>INSURANCE COMPANY TESTX</Nm>
<PstlAdr>
<StrtNm>TEST STREET 20</StrtNm>
<TwnNm>1234 AB TESTCITY</TwnNm>
<Ctry>NL</Ctry>
</PstlAdr>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>NL46ABNA0499998748</IBAN>
</Id>
</CdtrAcct>
</RltdPties>
<RltdAgts>
<CdtrAgt>
<FinInstnId>
<BIC>ABNANL2A</BIC>
</FinInstnId>
</CdtrAgt>
</RltdAgts>
<RmtInf>
<Ustrd>Insurance policy 857239PERIOD 01.01.2014 - 31.12.2014</Ustrd>
</RmtInf>
<AddtlTxInf>MKB Insurance 859239PERIOD 01.01.2014 - 31.12.2014</AddtlTxInf>
</TxDtls>
</NtryDtls>
</Ntry>
<Ntry>
<Amt Ccy="EUR">664.05</Amt>
<CdtDbtInd>DBIT</CdtDbtInd>
<RvslInd>true</RvslInd>
<Sts>BOOK</Sts>
<BookgDt>
<Dt>2014-01-05</Dt>
</BookgDt>
<ValDt>
<Dt>2014-01-05</Dt>
</ValDt>
<BkTxCd>
<Domn>
<Cd>PMNT</Cd>
<Fmly>
<Cd>IDDT</Cd>
<SubFmlyCd>UPDD</SubFmlyCd>
</Fmly>
</Domn>
<Prtry>
<Cd>EIST</Cd>
</Prtry>
</BkTxCd>
<NtryDtls>
<Btch>
<MsgId>2014/125</MsgId>
<PmtInfId>2018/125-20141229-NORM</PmtInfId>
<NbOfTxs>2</NbOfTxs>
<TtlAmt Ccy="EUR">664.05</TtlAmt>
<CdtDbtInd>DBIT</CdtDbtInd>
</Btch>
<TxDtls>
<Refs>
<InstrId>TESTBANK/NL/20141229/01206408</InstrId>
<EndToEndId>TESTBANK/NL/20141229/01206408</EndToEndId>
<MndtId>NL22ZZZ524885430000-C0125.1</MndtId>
</Refs>
<AmtDtls>
<TxAmt>
<Amt Ccy="EUR">564.05</Amt>
</TxAmt>
</AmtDtls>
<RltdPties>
<Cdtr>
<Nm>Test Customer</Nm>
<PstlAdr>
<Ctry>NL</Ctry>
</PstlAdr>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>NL46ABNA0499998748</IBAN>
</Id>
</CdtrAcct>
</RltdPties>
<RltdAgts>
<CdtrAgt>
<FinInstnId>
<BIC>ABNANL2A</BIC>
</FinInstnId>
</CdtrAgt>
</RltdAgts>
<RmtInf>
<Ustrd>Direct Debit S14 0410</Ustrd>
</RmtInf>
<RtrInf>
<Rsn>
<Cd>AC06</Cd>
</Rsn>
</RtrInf>
<AddtlTxInf>Direct debit S14 0410 AC07 Rek.nummer blokkade TESTBANK/NL/20141229/01206408</AddtlTxInf>
</TxDtls>
<TxDtls>
<Refs>
<InstrId>TESTBANK/NL/20141229/01206407</InstrId>
<EndToEndId>TESTBANK/NL/20141229/01206407</EndToEndId>
<MndtId>NL22ZZZ524885430000-C0125.2</MndtId>
</Refs>
<AmtDtls>
<TxAmt>
<Amt Ccy="EUR">100.00</Amt>
</TxAmt>
</AmtDtls>
<RltdPties>
<Cdtr>
<Nm>Test Customer</Nm>
<PstlAdr>
<Ctry>NL</Ctry>
</PstlAdr>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>NL46ABNA0499998748</IBAN>
</Id>
</CdtrAcct>
</RltdPties>
<RltdAgts>
<CdtrAgt>
<FinInstnId>
<BIC>ABNANL2A</BIC>
</FinInstnId>
</CdtrAgt>
</RltdAgts>
<RmtInf>
<Ustrd>Direct Debit S14 0410</Ustrd>
</RmtInf>
<RtrInf>
<Rsn>
<Cd>AC06</Cd>
</Rsn>
</RtrInf>
<AddtlTxInf>Direct debit S14 0410 AC07 Rek.nummer blokkade TESTBANK/NL/20141229/01206408</AddtlTxInf>
</TxDtls>
</NtryDtls>
</Ntry>
<Ntry>
<Amt Ccy="EUR">1405.31</Amt>
<CdtDbtInd>CRDT</CdtDbtInd>
<Sts>BOOK</Sts>
<BookgDt>
<Dt>2014-01-05</Dt>
</BookgDt>
<ValDt>
<Dt>2014-01-05</Dt>
</ValDt>
<BkTxCd>
<Domn>
<Cd>PMNT</Cd>
<Fmly>
<Cd>RCDT</Cd>
<SubFmlyCd>ESCT</SubFmlyCd>
</Fmly>
</Domn>
<Prtry>
<Cd>ET</Cd>
</Prtry>
</BkTxCd>
<NtryDtls>
<TxDtls>
<Refs>
<InstrId>INNDNL2U20140105000217200000708</InstrId>
<EndToEndId>115</EndToEndId>
</Refs>
<AmtDtls>
<TxAmt>
<Amt Ccy="EUR">1405.31</Amt>
</TxAmt>
</AmtDtls>
<RltdPties>
<Dbtr>
<Nm>3rd party Media</Nm>
<PstlAdr>
<StrtNm>SOMESTREET 570-A</StrtNm>
<TwnNm>1276 ML HOUSCITY</TwnNm>
<Ctry>NL</Ctry>
</PstlAdr>
</Dbtr>
<DbtrAcct>
<Id>
<IBAN>NL69ABNA0522123643</IBAN>
</Id>
</DbtrAcct>
</RltdPties>
<RltdAgts>
<DbtrAgt>
<FinInstnId>
<BIC>ABNANL2A</BIC>
</FinInstnId>
</DbtrAgt>
</RltdAgts>
<AddtlTxInf>#RD PARTY MEDIA CUSNO 90782 4210773</AddtlTxInf>
</TxDtls>
</NtryDtls>
</Ntry>
</Stmt>
</BkToCstmrStmt>
</Document>

30
account_bank_statement_import_camt_oca/tests/test_import_bank_statement.py

@ -113,6 +113,35 @@ class TestImport(TransactionCase):
'bank_account_id': bank.id,
})
def test_statement_import_more_than_one_xml_tag(self):
"""Test correct statements creation for more than one xml document
in import file."""
testfile = get_module_resource(
'account_bank_statement_import_camt_oca',
'test_files',
'test-camt053-more-than-one-xml',
)
with open(testfile, 'rb') as datafile:
action = self.env['account.bank.statement.import'].create({
'data_file': base64.b64encode(datafile.read())
}).import_file()
statement_lines = self.env['account.bank.statement'].browse(
action['context']['statement_ids']
).mapped('line_ids')
self.assertEqual(len(statement_lines), 8)
self.assertTrue(any(
all(
line[key] == self.transactions[0][key]
for key in ['amount', 'date', 'ref']
) and
line.bank_account_id.acc_number ==
self.transactions[0]['account_number']
for line in statement_lines
))
def test_statement_import(self):
"""Test correct creation of single statement."""
testfile = get_module_resource(
@ -128,6 +157,7 @@ class TestImport(TransactionCase):
statement_lines = self.env['account.bank.statement'].browse(
action['context']['statement_ids']
).line_ids
self.assertEqual(len(statement_lines), 4)
self.assertTrue(any(
all(
line[key] == self.transactions[0][key]

Loading…
Cancel
Save