121 lines
4.1 KiB
Python
121 lines
4.1 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
from dateutil.parser import parse
|
|
from lxml import etree
|
|
|
|
from trytond.transaction import Transaction
|
|
|
|
__all__ = ['SEPAHandler', 'CAMT054']
|
|
|
|
|
|
class SEPAHandler(object):
|
|
|
|
def __init__(self, source):
|
|
for event, element in etree.iterparse(source):
|
|
self.handle(event, element)
|
|
|
|
def handle(self, event, element):
|
|
raise NotImplementedError
|
|
|
|
|
|
class CAMT054(SEPAHandler):
|
|
msg_id = None
|
|
|
|
def __init__(self, source, Payment):
|
|
self.Payment = Payment
|
|
super().__init__(source)
|
|
|
|
def handle(self, event, element):
|
|
tag = etree.QName(element)
|
|
if tag.localname == 'GrpHdr':
|
|
self.msg_id = element.find('./{%s}MsgId' % tag.namespace).text
|
|
element.clear()
|
|
elif tag.localname == 'Ntry':
|
|
self.handle_entry(element)
|
|
element.clear()
|
|
|
|
def handle_entry(self, element):
|
|
tag = etree.QName(element)
|
|
failed, succeeded = [], []
|
|
date_value = self.date_value(element)
|
|
|
|
for transaction in element.findall('.//{%s}TxDtls' % tag.namespace):
|
|
payments = self.get_payments(transaction)
|
|
if self.is_returned(transaction):
|
|
for payment in payments:
|
|
self.set_return_information(payment, transaction)
|
|
failed.extend(payments)
|
|
else:
|
|
succeeded.extend(payments)
|
|
|
|
if failed:
|
|
self.Payment.save(failed)
|
|
self.Payment.fail(failed)
|
|
if succeeded:
|
|
with Transaction().set_context(date_value=date_value):
|
|
self.Payment.succeed(succeeded)
|
|
|
|
def get_payment_kind(self, element):
|
|
camt_ns = etree.QName(element).namespace
|
|
for path in [
|
|
'./camt:CdtDbtInd',
|
|
'./ancestor::camt:NtryDtls/camt:Btch/camt:CdtDbtInd',
|
|
'./ancestor::camt:Ntry/camt:CdtDbtInd',
|
|
]:
|
|
cdtdbtind = element.xpath(path, namespaces={'camt': camt_ns})
|
|
if cdtdbtind:
|
|
return self._kinds[cdtdbtind[0].text]
|
|
_kinds = {
|
|
'CRDT': 'payable',
|
|
'DBIT': 'receivable',
|
|
}
|
|
|
|
def get_payments(self, transaction):
|
|
tag = etree.QName(transaction)
|
|
instr_id = transaction.find(
|
|
'.//{%s}InstrId' % tag.namespace)
|
|
end_to_end_id = transaction.find(
|
|
'.//{%s}EndToEndId' % tag.namespace)
|
|
payment_kind = self.get_payment_kind(transaction)
|
|
if instr_id is not None:
|
|
return self.Payment.search([
|
|
('sepa_instruction_id', '=', instr_id.text),
|
|
('kind', '=', payment_kind),
|
|
])
|
|
elif end_to_end_id is not None:
|
|
return self.Payment.search([
|
|
('sepa_end_to_end_id', '=', end_to_end_id.text),
|
|
('kind', '=', payment_kind),
|
|
])
|
|
return []
|
|
|
|
def date_value(self, element):
|
|
tag = etree.QName(element)
|
|
date = element.find('./{%(ns)s}ValDt/{%(ns)s}Dt'
|
|
% {'ns': tag.namespace})
|
|
if date is not None:
|
|
return parse(date.text).date()
|
|
else:
|
|
datetime = element.find('./{%(ns)s}ValDt/{%(ns)s}DtTm'
|
|
% {'ns': tag.namespace})
|
|
if datetime:
|
|
return parse(datetime.text).date()
|
|
|
|
def is_returned(self, element):
|
|
tag = etree.QName(element)
|
|
return_reason = element.find('.//{%s}RtrInf' % tag.namespace)
|
|
return return_reason is not None
|
|
|
|
def set_return_information(self, payment, element):
|
|
tag = etree.QName(element)
|
|
|
|
reason_code = element.find(
|
|
'.//{%(ns)s}RtrInf/{%(ns)s}Rsn/{%(ns)s}Cd' % {'ns': tag.namespace})
|
|
if reason_code is not None:
|
|
payment.sepa_return_reason_code = reason_code.text
|
|
|
|
reason_information = element.find(
|
|
'.//{%(ns)s}RtrInf/{%(ns)s}AddtlInf' % {'ns': tag.namespace})
|
|
if reason_information is not None:
|
|
payment.sepa_return_reason_information = reason_information.text
|