first commit
This commit is contained in:
218
modules/account_statement_sepa/account.py
Normal file
218
modules/account_statement_sepa/account.py
Normal file
@@ -0,0 +1,218 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import datetime as dt
|
||||
from decimal import Decimal
|
||||
from io import BytesIO
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from trytond.i18n import gettext
|
||||
from trytond.modules.account_statement.exceptions import ImportStatementError
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
|
||||
|
||||
class StatementImportStart(metaclass=PoolMeta):
|
||||
__name__ = 'account.statement.import.start'
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.file_format.selection.extend([
|
||||
('camt_052_001', "CAMT.052.001"),
|
||||
('camt_053_001', "CAMT.053.001"),
|
||||
('camt_054_001', "CAMT.054.001"),
|
||||
])
|
||||
|
||||
|
||||
class StatementImport(metaclass=PoolMeta):
|
||||
__name__ = 'account.statement.import'
|
||||
|
||||
def parse_camt(self):
|
||||
file_ = BytesIO(self.start.file_)
|
||||
tree = etree.parse(file_)
|
||||
root = tree.getroot()
|
||||
namespaces = dict(root.nsmap)
|
||||
namespaces['ns'] = namespaces.pop(None)
|
||||
for camt_statement in root.xpath(
|
||||
'./ns:BkToCstmrStmt/ns:Stmt | '
|
||||
'./ns:BkToCstmrAcctRpt/ns:Rpt | '
|
||||
'./ns:BkToCstmrDbtCdtNtfctn/ns:Ntfctn', namespaces=namespaces):
|
||||
statement = self.camt_statement(camt_statement)
|
||||
origins = []
|
||||
for entry in camt_statement.iterfind('./{*}Ntry'):
|
||||
origins.extend(self.camt_origin(camt_statement, entry))
|
||||
if origins:
|
||||
statement.number_of_lines = len(origins)
|
||||
statement.origins = origins
|
||||
yield statement
|
||||
|
||||
parse_camt_052_001 = parse_camt
|
||||
parse_camt_053_001 = parse_camt
|
||||
parse_camt_054_001 = parse_camt
|
||||
|
||||
def camt_statement(self, camt_statement):
|
||||
pool = Pool()
|
||||
Statement = pool.get('account.statement')
|
||||
Journal = pool.get('account.statement.journal')
|
||||
|
||||
statement = Statement()
|
||||
statement.name = camt_statement.findtext('./{*}Id')
|
||||
statement.company = self.start.company
|
||||
account_number = self.camt_account_number(camt_statement)
|
||||
account_currency = self.camt_account_currency(camt_statement)
|
||||
statement.journal = Journal.get_by_bank_account(
|
||||
statement.company, account_number, currency=account_currency)
|
||||
if not statement.journal:
|
||||
raise ImportStatementError(
|
||||
gettext('account_statement.msg_import_no_journal',
|
||||
account=account_number))
|
||||
statement.date = self.camt_statement_date(camt_statement)
|
||||
statement.start_balance, statement.end_balance = (
|
||||
self.camt_statement_balances(camt_statement))
|
||||
statement.total_amount = self.camt_statement_total(camt_statement)
|
||||
return statement
|
||||
|
||||
def _camt_amount(self, entry, detail=None):
|
||||
if detail is not None:
|
||||
amount = Decimal(detail.findtext('./{*}AmtDtls/{*}TxAmt/{*}Amt'))
|
||||
else:
|
||||
amount = Decimal(entry.findtext('./{*}Amt'))
|
||||
if entry.findtext('./{*}CdtDbtInd') == 'DBIT':
|
||||
amount *= -1
|
||||
return amount
|
||||
|
||||
def camt_account_number(self, camt_statement):
|
||||
number = camt_statement.findtext('./{*}Acct/{*}Id/{*}IBAN')
|
||||
if not number:
|
||||
number = camt_statement.findtext('./{*}Acct/{*}Id/{*}Othr/{*}Id')
|
||||
return number
|
||||
|
||||
def camt_account_currency(self, camt_statement):
|
||||
return camt_statement.findtext('./{*}Acct/{*}Ccy')
|
||||
|
||||
def camt_statement_date(self, camt_statement):
|
||||
pool = Pool()
|
||||
Date = pool.get('ir.date')
|
||||
datetime = camt_statement.findtext('./{*}CreDtTm')
|
||||
if datetime:
|
||||
return dt.datetime.fromisoformat(datetime).date()
|
||||
else:
|
||||
return Date.today()
|
||||
|
||||
def camt_statement_balances(self, camt_statement):
|
||||
namespaces = dict(camt_statement.nsmap)
|
||||
namespaces['ns'] = namespaces.pop(None)
|
||||
start_balance = end_balance = None
|
||||
for code in [
|
||||
'OPBD', # Opening Balance
|
||||
'PRCD', # Previous Closing Balance
|
||||
'CLBD', # Closing Balance
|
||||
'ITBD', # Interim Balance
|
||||
]:
|
||||
balances = camt_statement.xpath(
|
||||
f'./ns:Bal/ns:Tp/ns:CdOrPrtry/ns:Cd[text()="{code}"]'
|
||||
'/../../..', namespaces=namespaces)
|
||||
if balances:
|
||||
if code in {'OPBD', 'PRCD'}:
|
||||
start_balance = self._camt_amount(balances[0])
|
||||
elif code == 'CLBD':
|
||||
end_balance = self._camt_amount(balances[-1])
|
||||
else:
|
||||
start_balance = self._camt_amount(balances[0])
|
||||
end_balance = self._camt_amount(balances[-1])
|
||||
return start_balance, end_balance
|
||||
|
||||
def camt_statement_total(self, camt_statement):
|
||||
total = camt_statement.find(
|
||||
'./{*}TxsSummry/{*}TtlNtries/{*}TtlNetNtry')
|
||||
if total is not None:
|
||||
return self._camt_amount(total)
|
||||
|
||||
def camt_origin(self, camt_statement, entry):
|
||||
pool = Pool()
|
||||
Date = pool.get('ir.date')
|
||||
Origin = pool.get('account.statement.origin')
|
||||
|
||||
details = entry.findall('./{*}NtryDtls/{*}TxDtls')
|
||||
if not details:
|
||||
details = [None]
|
||||
for detail in details:
|
||||
origin = Origin()
|
||||
origin.number = entry.findtext('./{*}NtryRef')
|
||||
origin.date = Date.today()
|
||||
booking_date = entry.find('./{*}BookgDt')
|
||||
if booking_date is not None:
|
||||
if booking_date.find('./{*}Dt') is not None:
|
||||
origin.date = dt.date.fromisoformat(
|
||||
booking_date.findtext('./{*}Dt'))
|
||||
elif booking_date.find('./{*}DtTm') is not None:
|
||||
origin.date = dt.date.fromisoformat(
|
||||
booking_date.findtext('./{*}DtTm')).date()
|
||||
origin.amount = self._camt_amount(entry, detail)
|
||||
origin.description = self.camt_description(camt_statement, entry)
|
||||
if detail is not None:
|
||||
origin.party = self.camt_party(camt_statement, entry, detail)
|
||||
origin.information = self.camt_information(
|
||||
camt_statement, entry, detail)
|
||||
|
||||
yield origin
|
||||
|
||||
def camt_description(self, camt_statement, entry):
|
||||
return entry.findtext('./{*}AddtlNtryInf')
|
||||
|
||||
def camt_party(self, camt_statement, entry, detail):
|
||||
pool = Pool()
|
||||
AccountNumber = pool.get('bank.account.number')
|
||||
|
||||
path = {
|
||||
'DBIT': './{*}RltdPties/{*}CdtrAcct/{*}Id/{*}IBAN',
|
||||
'CRDT': './{*}RltdPties/{*}DbtrAcct/{*}Id/{*}IBAN',
|
||||
}[entry.findtext('./{*}CdtDbtInd')]
|
||||
number = detail.findtext(path)
|
||||
if number:
|
||||
numbers = AccountNumber.search(['OR',
|
||||
('number', '=', number),
|
||||
('number_compact', '=', number),
|
||||
])
|
||||
if len(numbers) == 1:
|
||||
number, = numbers
|
||||
if number.account.owners:
|
||||
return number.account.owners[0]
|
||||
|
||||
def camt_information(self, camt_statement, entre, detail):
|
||||
information = {}
|
||||
for key, path in [
|
||||
('camt_message_identification', './{*}Refs/{*}MsgId'),
|
||||
('camt_account_service_reference', './{*}Refs/{*}AcctSvcrRef'),
|
||||
('camt_payment_information_identification',
|
||||
'./{*}Refs/{*}PmtInfId'),
|
||||
('camt_instruction_identification', './{*}Refs/{*}InstrId'),
|
||||
('camt_end_to_end_identification', './{*}Refs/{*}EndToEndId'),
|
||||
('camt_transaction_identification', './{*}Refs/{*}TxId'),
|
||||
('camt_mandate_identification', './{*}Refs/{*}MndtId'),
|
||||
('camt_cheque_number', './{*}Refs/{*}ChqNb'),
|
||||
('camt_clearing_system_reference', './{*}Refs/{*}ClrSysRef'),
|
||||
('camt_account_owner_transaction_identification',
|
||||
'./{*}Refs/{*}AcctOwnrTxId'),
|
||||
('camt_account_servicer_transaction_identification',
|
||||
'./{*}Refs/{*}AcctSvcrTxId'),
|
||||
('camt_market_infrastructure_transaction_identification',
|
||||
'./{*}Refs/{*}MktInfrstrctrTxId'),
|
||||
('camt_processing_identification', './{*}Refs/{*}PrcgId'),
|
||||
('camt_remittance_information', './{*}RmtInf/{*}Ustrd'),
|
||||
('camt_additional_transaction_information', './{*}AddtlTxInf'),
|
||||
('camt_debtor_name', './{*}RltdPties/{*}Dbtr//{*}Nm'),
|
||||
('camt_ultimate_debtor_name',
|
||||
'./{*}RltdPties/{*}UltmtDbtr//{*}Nm'),
|
||||
('camt_debtor_iban',
|
||||
'./{*}RltdPties/{*}DbtrAcct/{*}Id/{*}IBAN'),
|
||||
('camt_creditor_name', './{*}RltdPties/{*}Cdtr//{*}Nm'),
|
||||
('camt_ultimate_creditor_name',
|
||||
'./{*}RltdPties/{*}UltmtCdtr//{*}Nm'),
|
||||
('camt_creditor_iban',
|
||||
'./{*}RltdPties/{*}CdtrAcct/{*}Id/{*}IBAN'),
|
||||
]:
|
||||
value = detail.findtext(path)
|
||||
if value:
|
||||
information[key] = value
|
||||
return information
|
||||
Reference in New Issue
Block a user