Files
tryton/payment.py
T

787 lines
26 KiB
Python
Raw Normal View History

2014-12-02 12:54:47 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2014-03-10 23:12:36 +01:00
import datetime
import os
import unicodedata
2014-03-10 23:12:36 +01:00
from itertools import groupby
2014-08-04 16:47:44 +02:00
from io import BytesIO
2014-03-10 23:12:36 +01:00
import genshi
import genshi.template
2014-08-04 16:47:44 +02:00
from lxml import etree
2014-03-10 23:12:36 +01:00
from sql import Literal
from trytond.pool import PoolMeta, Pool
from trytond.model import (ModelSQL, ModelView, Workflow, fields, dualmethod,
Unique)
2014-03-10 23:12:36 +01:00
from trytond.pyson import Eval, If
from trytond.transaction import Transaction
2014-07-17 18:57:56 +02:00
from trytond.tools import reduce_ids, grouped_slice
2014-07-09 00:48:35 +02:00
from trytond import backend
2014-09-19 18:41:57 +02:00
from trytond.modules.company import CompanyReport
2014-08-04 16:47:44 +02:00
from .sepa_handler import CAMT054
2014-03-10 23:12:36 +01:00
2014-09-19 18:41:57 +02:00
__all__ = ['Journal', 'Group', 'Payment', 'Mandate', 'Message',
'MandateReport']
2014-03-10 23:12:36 +01:00
2016-03-06 22:51:42 +01:00
# XXX fix: https://genshi.edgewall.org/ticket/582
from genshi.template.astutil import ASTCodeGenerator, ASTTransformer
if not hasattr(ASTCodeGenerator, 'visit_NameConstant'):
def visit_NameConstant(self, node):
if node.value is None:
self._write('None')
elif node.value is True:
self._write('True')
elif node.value is False:
self._write('False')
else:
raise Exception("Unknown NameConstant %r" % (node.value,))
ASTCodeGenerator.visit_NameConstant = visit_NameConstant
if not hasattr(ASTTransformer, 'visit_NameConstant'):
# Re-use visit_Name because _clone is deleted
ASTTransformer.visit_NameConstant = ASTTransformer.visit_Name
2014-03-10 23:12:36 +01:00
2018-06-26 19:11:43 +02:00
class Journal(metaclass=PoolMeta):
2014-03-10 23:12:36 +01:00
__name__ = 'account.payment.journal'
company_party = fields.Function(fields.Many2One('party.party',
'Company Party'), 'on_change_with_company_party')
sepa_bank_account_number = fields.Many2One('bank.account.number',
'Bank Account Number', states={
'required': Eval('process_method') == 'sepa',
'invisible': Eval('process_method') != 'sepa',
},
domain=[
('type', '=', 'iban'),
('account.owners', '=', Eval('company_party')),
],
depends=['process_method', 'company_party'])
sepa_payable_flavor = fields.Selection([
(None, ''),
('pain.001.001.03', 'pain.001.001.03'),
('pain.001.001.05', 'pain.001.001.05'),
('pain.001.003.03', 'pain.001.003.03'),
2014-03-10 23:12:36 +01:00
], 'Payable Flavor', states={
'required': Eval('process_method') == 'sepa',
'invisible': Eval('process_method') != 'sepa',
},
2014-04-29 19:33:20 +02:00
translate=False,
2014-03-10 23:12:36 +01:00
depends=['process_method'])
sepa_receivable_flavor = fields.Selection([
(None, ''),
('pain.008.001.02', 'pain.008.001.02'),
('pain.008.001.04', 'pain.008.001.04'),
('pain.008.003.02', 'pain.008.003.02'),
2014-03-10 23:12:36 +01:00
], 'Receivable Flavor', states={
'required': Eval('process_method') == 'sepa',
'invisible': Eval('process_method') != 'sepa',
},
2014-04-29 19:33:20 +02:00
translate=False,
2014-03-10 23:12:36 +01:00
depends=['process_method'])
sepa_batch_booking = fields.Boolean('Batch Booking', states={
'invisible': Eval('process_method') != 'sepa',
},
depends=['process_method'])
sepa_charge_bearer = fields.Selection([
('DEBT', 'Debtor'),
('CRED', 'Creditor'),
('SHAR', 'Shared'),
('SLEV', 'Service Level'),
], 'Charge Bearer', states={
'required': Eval('process_method') == 'sepa',
'invisible': Eval('process_method') != 'sepa',
},
depends=['process_method'])
@classmethod
def __setup__(cls):
super(Journal, cls).__setup__()
sepa_method = ('sepa', 'SEPA')
if sepa_method not in cls.process_method.selection:
cls.process_method.selection.append(sepa_method)
@classmethod
def default_company_party(cls):
pool = Pool()
Company = pool.get('company.company')
company_id = cls.default_company()
if company_id:
return Company(company_id).party.id
@fields.depends('company')
def on_change_with_company_party(self, name=None):
if self.company:
return self.company.party.id
@staticmethod
def default_sepa_charge_bearer():
return 'SLEV'
def remove_comment(stream):
for kind, data, pos in stream:
if kind is genshi.core.COMMENT:
continue
yield kind, data, pos
loader = genshi.template.TemplateLoader(
os.path.join(os.path.dirname(__file__), 'template'),
auto_reload=True)
2018-06-26 19:11:43 +02:00
class Group(metaclass=PoolMeta):
2014-03-10 23:12:36 +01:00
__name__ = 'account.payment.group'
2014-07-09 00:48:35 +02:00
sepa_messages = fields.One2Many('account.payment.sepa.message', 'origin',
'SEPA Messages', readonly=True,
domain=[('company', '=', Eval('company', -1))],
states={
'invisible': ~Eval('sepa_messages'),
},
depends=['company'])
2014-03-10 23:12:36 +01:00
@classmethod
def __setup__(cls):
super(Group, cls).__setup__()
cls._error_messages.update({
'no_mandate': 'No valid mandate for payment "%s"',
'no_bank_account_number': 'No bank account for payment "%s".',
2014-03-10 23:12:36 +01:00
})
cls._buttons.update({
'generate_message': {},
})
2014-03-10 23:12:36 +01:00
def get_sepa_template(self):
if self.kind == 'payable':
return loader.load('%s.xml' % self.journal.sepa_payable_flavor)
elif self.kind == 'receivable':
return loader.load('%s.xml' % self.journal.sepa_receivable_flavor)
def process_sepa(self):
pool = Pool()
Payment = pool.get('account.payment')
if self.kind == 'receivable':
2014-11-10 23:27:17 +01:00
mandates = Payment.get_sepa_mandates(self.payments)
for payment, mandate in zip(self.payments, mandates):
2014-03-10 23:12:36 +01:00
if not mandate:
self.raise_user_error('no_mandate', payment.rec_name)
2017-01-10 13:53:37 +01:00
# Write one by one because mandate.sequence_type must be
# recomputed each time
2014-03-10 23:12:36 +01:00
Payment.write([payment], {
'sepa_mandate': mandate,
'sepa_mandate_sequence_type': mandate.sequence_type,
2014-03-10 23:12:36 +01:00
})
else:
for payment in self.payments:
if not payment.sepa_bank_account_number:
self.raise_user_error(
'no_bank_account_number', payment.rec_name)
self.generate_message(_save=False)
@dualmethod
@ModelView.button
def generate_message(cls, groups, _save=True):
pool = Pool()
Message = pool.get('account.payment.sepa.message')
for group in groups:
tmpl = group.get_sepa_template()
if not tmpl:
raise NotImplementedError
if not group.sepa_messages:
group.sepa_messages = ()
message = tmpl.generate(group=group,
datetime=datetime, normalize=unicodedata.normalize,
).filter(remove_comment).render()
2014-12-03 16:16:17 +01:00
message = Message(message=message, type='out', state='waiting',
company=group.company)
group.sepa_messages += (message,)
if _save:
cls.save(groups)
2014-03-10 23:12:36 +01:00
@property
def sepa_initiating_party(self):
return self.company.party
def sepa_group_payment_key(self, payment):
key = (('date', payment.date),)
if self.kind == 'receivable':
key += (('sequence_type', payment.sepa_mandate_sequence_type),)
key += (('scheme', payment.sepa_mandate.scheme),)
2014-03-10 23:12:36 +01:00
return key
def sepa_group_payment_id(self, key):
payment_id = str(key['date'].toordinal())
if self.kind == 'receivable':
payment_id += '-' + key['sequence_type']
return payment_id
@property
def sepa_payments(self):
pool = Pool()
Payment = pool.get('account.payment')
2014-03-10 23:12:36 +01:00
keyfunc = self.sepa_group_payment_key
# re-browse to align cache
payments = Payment.browse(sorted(self.payments, key=keyfunc))
2014-03-10 23:12:36 +01:00
for key, grouped_payments in groupby(payments, key=keyfunc):
yield dict(key), list(grouped_payments)
2018-06-26 19:11:43 +02:00
class Payment(metaclass=PoolMeta):
2014-03-10 23:12:36 +01:00
__name__ = 'account.payment'
sepa_mandate = fields.Many2One('account.payment.sepa.mandate', 'Mandate',
ondelete='RESTRICT',
domain=[
('party', '=', Eval('party', -1)),
2014-12-03 16:16:17 +01:00
('company', '=', Eval('company', -1)),
2014-03-10 23:12:36 +01:00
],
2014-12-03 16:16:17 +01:00
depends=['party', 'company'])
sepa_mandate_sequence_type = fields.Char('Mandate Sequence Type',
readonly=True)
2014-08-04 16:47:44 +02:00
sepa_return_reason_code = fields.Char('Return Reason Code', readonly=True,
states={
'invisible': (~Eval('sepa_return_reason_code')
& (Eval('state') != 'failed')),
},
depends=['state'])
sepa_return_reason_information = fields.Text('Return Reason Information',
readonly=True,
states={
'invisible': (~Eval('sepa_return_reason_information')
& (Eval('state') != 'failed')),
},
depends=['state'])
sepa_end_to_end_id = fields.Function(fields.Char('SEPA End To End ID'),
'get_sepa_end_to_end_id', searcher='search_end_to_end_id')
sepa_instruction_id = fields.Function(fields.Char('SEPA Instruction ID'),
'get_sepa_instruction_id', searcher='search_sepa_instruction_id')
2014-03-10 23:12:36 +01:00
2014-11-10 23:27:17 +01:00
@classmethod
def copy(cls, payments, default=None):
if default is None:
default = {}
2018-09-01 01:02:03 +02:00
else:
default = default.copy()
2014-11-10 23:27:17 +01:00
default.setdefault('sepa_mandate_sequence_type', None)
return super(Payment, cls).copy(payments, default=default)
2014-03-10 23:12:36 +01:00
@classmethod
def get_sepa_mandates(cls, payments):
mandates = []
for payment in payments:
2014-11-10 23:27:17 +01:00
if payment.sepa_mandate:
if payment.sepa_mandate.is_valid:
mandate = payment.sepa_mandate
else:
mandate = None
2014-03-10 23:12:36 +01:00
else:
2014-11-10 23:27:17 +01:00
for mandate in payment.party.sepa_mandates:
if mandate.is_valid:
break
else:
mandate = None
2014-03-10 23:12:36 +01:00
mandates.append(mandate)
return mandates
2014-08-04 16:47:44 +02:00
def get_sepa_end_to_end_id(self, name):
2014-09-30 09:50:43 +02:00
return str(self.id)
2014-03-10 23:12:36 +01:00
2014-08-04 16:47:44 +02:00
@classmethod
def search_end_to_end_id(cls, name, domain):
table = cls.__table__()
_, operator, value = domain
cast = cls.sepa_end_to_end_id._field.sql_type().base
2014-08-04 16:47:44 +02:00
Operator = fields.SQL_OPERATORS[operator]
query = table.select(table.id,
where=Operator(table.id.cast(cast), value))
return [('id', 'in', query)]
get_sepa_instruction_id = get_sepa_end_to_end_id
search_sepa_instruction_id = search_end_to_end_id
2014-03-10 23:12:36 +01:00
@property
def sepa_remittance_information(self):
if self.description:
return self.description
elif self.line and self.line.origin:
return self.line.origin.rec_name
@property
def sepa_bank_account_number(self):
2014-06-28 20:05:07 +02:00
if self.kind == 'receivable':
if self.sepa_mandate:
return self.sepa_mandate.account_number
else:
for account in self.party.bank_accounts:
for number in account.numbers:
if number.type == 'iban':
return number
2014-03-10 23:12:36 +01:00
2014-11-19 21:19:16 +01:00
@property
def rejected(self):
return (self.state == 'failed'
and self.sepa_return_reason_code
and self.sepa_return_reason_information == '/RTYP/RJCT')
2015-02-27 11:55:08 +01:00
def create_clearing_move(self, date=None):
if not date:
date = Transaction().context.get('date_value')
return super(Payment, self).create_clearing_move(date=date)
2014-03-10 23:12:36 +01:00
class Mandate(Workflow, ModelSQL, ModelView):
'SEPA Mandate'
__name__ = 'account.payment.sepa.mandate'
party = fields.Many2One('party.party', 'Party', required=True, select=True,
states={
'readonly': Eval('state').in_(
['requested', 'validated', 'canceled']),
},
depends=['state'])
account_number = fields.Many2One('bank.account.number', 'Account Number',
ondelete='RESTRICT',
2014-03-10 23:12:36 +01:00
states={
'readonly': Eval('state').in_(['validated', 'canceled']),
'required': Eval('state') == 'validated',
},
domain=[
('type', '=', 'iban'),
('account.owners', '=', Eval('party')),
],
depends=['state', 'party'])
identification = fields.Char('Identification', size=35,
states={
'readonly': Eval('identification_readonly', True),
2014-03-10 23:12:36 +01:00
'required': Eval('state') == 'validated',
},
depends=['state', 'identification_readonly'])
identification_readonly = fields.Function(fields.Boolean(
'Identification Readonly'), 'get_identification_readonly')
2014-03-10 23:12:36 +01:00
company = fields.Many2One('company.company', 'Company', required=True,
select=True,
domain=[
('id', If(Eval('context', {}).contains('company'), '=', '!='),
Eval('context', {}).get('company', -1)),
],
states={
'readonly': Eval('state') != 'draft',
},
depends=['state'])
type = fields.Selection([
('recurrent', 'Recurrent'),
('one-off', 'One-off'),
], 'Type',
states={
'readonly': Eval('state').in_(['validated', 'canceled']),
},
depends=['state'])
2016-10-11 18:14:27 +02:00
sequence_type_rcur = fields.Boolean(
"Always use RCUR",
states={
'invisible': Eval('type') == 'one-off',
},
depends=['type'])
scheme = fields.Selection([
('CORE', 'Core'),
('B2B', 'Business to Business'),
2015-09-29 12:33:05 +02:00
], 'Scheme', required=True,
states={
'readonly': Eval('state').in_(['validated', 'canceled']),
},
depends=['state'])
2014-09-19 18:41:57 +02:00
scheme_string = scheme.translated('scheme')
2014-03-10 23:12:36 +01:00
signature_date = fields.Date('Signature Date',
states={
'readonly': Eval('state').in_(['validated', 'canceled']),
'required': Eval('state') == 'validated',
},
depends=['state'])
state = fields.Selection([
('draft', 'Draft'),
('requested', 'Requested'),
('validated', 'Validated'),
('canceled', 'Canceled'),
], 'State', readonly=True)
payments = fields.One2Many('account.payment', 'sepa_mandate', 'Payments')
has_payments = fields.Function(fields.Boolean('Has Payments'),
'has_payments')
@classmethod
def __setup__(cls):
super(Mandate, cls).__setup__()
cls._transitions |= set((
('draft', 'requested'),
('requested', 'validated'),
('validated', 'canceled'),
('requested', 'canceled'),
('requested', 'draft'),
))
cls._buttons.update({
'cancel': {
'invisible': ~Eval('state').in_(
['requested', 'validated']),
2018-02-19 11:37:41 +01:00
'depends': ['state'],
2014-03-10 23:12:36 +01:00
},
'draft': {
'invisible': Eval('state') != 'requested',
2018-02-19 11:37:41 +01:00
'depends': ['state'],
2014-03-10 23:12:36 +01:00
},
'request': {
'invisible': Eval('state') != 'draft',
2018-02-19 11:37:41 +01:00
'depends': ['state'],
2014-03-10 23:12:36 +01:00
},
'validate_mandate': {
'invisible': Eval('state') != 'requested',
2018-02-19 11:37:41 +01:00
'depends': ['state'],
2014-03-10 23:12:36 +01:00
},
})
t = cls.__table__()
cls._sql_constraints = [
('identification_unique', Unique(t, t.company, t.identification),
'The identification of the SEPA mandate must be unique '
'in a company.'),
]
2014-03-10 23:12:36 +01:00
cls._error_messages.update({
'delete_draft_canceled': ('You can not delete mandate "%s" '
'because it is not in draft or canceled state.'),
})
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_type():
return 'recurrent'
2016-10-11 18:14:27 +02:00
@classmethod
def default_sequence_type_rcur(cls):
return False
@staticmethod
def default_scheme():
return 'CORE'
2014-03-10 23:12:36 +01:00
@staticmethod
def default_state():
return 'draft'
@staticmethod
def default_identification_readonly():
pool = Pool()
Configuration = pool.get('account.configuration')
config = Configuration(1)
return bool(config.sepa_mandate_sequence)
def get_identification_readonly(self, name):
return bool(self.identification)
2017-10-31 12:16:13 +01:00
def get_rec_name(self, name):
if self.identification:
return self.identification
return '(%s)' % self.id
@classmethod
def search_rec_name(cls, name, clause):
return [tuple(('identification',)) + tuple(clause[1:])]
@classmethod
def create(cls, vlist):
pool = Pool()
Sequence = pool.get('ir.sequence')
Configuration = pool.get('account.configuration')
config = Configuration(1)
vlist = [v.copy() for v in vlist]
for values in vlist:
if (config.sepa_mandate_sequence
and not values.get('identification')):
values['identification'] = Sequence.get_id(
config.sepa_mandate_sequence.id)
# Prevent raising false unique constraint
if values.get('identification') == '':
values['identification'] = None
return super(Mandate, cls).create(vlist)
@classmethod
def write(cls, *args):
actions = iter(args)
args = []
for mandates, values in zip(actions, actions):
# Prevent raising false unique constraint
if values.get('identification') == '':
values = values.copy()
values['identification'] = None
args.extend((mandates, values))
super(Mandate, cls).write(*args)
2014-09-19 16:40:46 +02:00
@classmethod
def copy(cls, mandates, default=None):
if default is None:
default = {}
2018-09-01 01:02:03 +02:00
else:
default = default.copy()
2014-09-19 16:40:46 +02:00
default.setdefault('payments', [])
default.setdefault('signature_date', None)
default.setdefault('identification', None)
return super(Mandate, cls).copy(mandates, default=default)
2014-03-10 23:12:36 +01:00
@property
def is_valid(self):
if self.state == 'validated':
if self.type == 'one-off':
if not self.has_payments:
return True
else:
return True
return False
@property
def sequence_type(self):
if self.type == 'one-off':
return 'OOFF'
2016-10-11 18:14:27 +02:00
elif not self.sequence_type_rcur and (not self.payments
or all(not p.sepa_mandate_sequence_type for p in self.payments)
2014-11-19 21:19:16 +01:00
or all(p.rejected for p in self.payments)):
2014-03-10 23:12:36 +01:00
return 'FRST'
# TODO manage FNAL
else:
return 'RCUR'
@classmethod
def has_payments(self, mandates, name):
pool = Pool()
Payment = pool.get('account.payment')
payment = Payment.__table__
cursor = Transaction().connection.cursor()
2014-03-10 23:12:36 +01:00
has_payments = dict.fromkeys([m.id for m in mandates], False)
2014-07-17 18:57:56 +02:00
for sub_ids in grouped_slice(mandates):
2014-03-10 23:12:36 +01:00
red_sql = reduce_ids(payment.sepa_mandate, sub_ids)
cursor.execute(*payment.select(payment.sepa_mandate, Literal(True),
where=red_sql,
group_by=payment.sepa_mandate))
has_payments.update(cursor.fetchall())
return {'has_payments': has_payments}
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, mandates):
pass
@classmethod
@ModelView.button
@Workflow.transition('requested')
def request(cls, mandates):
pass
@classmethod
@ModelView.button
@Workflow.transition('validated')
def validate_mandate(cls, mandates):
pass
@classmethod
@ModelView.button
@Workflow.transition('canceled')
def cancel(cls, mandates):
# TODO must be automaticaly canceled 13 months after last collection
pass
@classmethod
def delete(cls, mandates):
for mandate in mandates:
if mandate.state not in ('draft', 'canceled'):
cls.raise_user_error('delete_draft_canceled', mandate.rec_name)
super(Mandate, cls).delete(mandates)
2014-07-09 00:48:35 +02:00
2014-09-19 18:41:57 +02:00
class MandateReport(CompanyReport):
__name__ = 'account.payment.sepa.mandate'
2014-07-09 00:48:35 +02:00
class Message(Workflow, ModelSQL, ModelView):
'SEPA Message'
__name__ = 'account.payment.sepa.message'
_states = {
'readonly': Eval('state') != 'draft',
}
_depends = ['state']
message = fields.Text('Message', states=_states, depends=_depends)
filename = fields.Function(fields.Char('Filename'), 'get_filename')
type = fields.Selection([
('in', 'IN'),
('out', 'OUT'),
], 'Type', required=True, states=_states, depends=_depends)
company = fields.Many2One('company.company', 'Company', required=True,
select=True,
domain=[
('id', If(Eval('context', {}).contains('company'), '=', '!='),
Eval('context', {}).get('company', -1)),
],
states={
'readonly': Eval('state') != 'draft',
},
depends=['state'])
origin = fields.Reference('Origin', selection='get_origin', select=True,
states=_states, depends=_depends)
state = fields.Selection([
('draft', 'Draft'),
('waiting', 'Waiting'),
('done', 'Done'),
('canceled', 'Canceled'),
], 'State', readonly=True, select=True)
@classmethod
def __setup__(cls):
super(Message, cls).__setup__()
cls._transitions |= {
('draft', 'waiting'),
('waiting', 'done'),
('waiting', 'draft'),
('draft', 'canceled'),
('waiting', 'canceled'),
}
cls._buttons.update({
'cancel': {
'invisible': ~Eval('state').in_(['draft', 'waiting']),
2018-02-19 11:37:41 +01:00
'depends': ['state'],
2014-07-09 00:48:35 +02:00
},
'draft': {
'invisible': Eval('state') != 'waiting',
2018-02-19 11:37:41 +01:00
'depends': ['state'],
2014-07-09 00:48:35 +02:00
},
'wait': {
'invisible': Eval('state') != 'draft',
2018-02-19 11:37:41 +01:00
'depends': ['state'],
2014-07-09 00:48:35 +02:00
},
'do': {
'invisible': Eval('state') != 'waiting',
2018-02-19 11:37:41 +01:00
'depends': ['state'],
2014-07-09 00:48:35 +02:00
},
})
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
cursor = Transaction().connection.cursor()
2014-07-09 00:48:35 +02:00
pool = Pool()
Group = pool.get('account.payment.group')
super(Message, cls).__register__(module_name)
# Migration from 3.2
if TableHandler.table_exist(Group._table):
2018-07-24 22:01:07 +02:00
group_table = Group.__table_handler__(module_name)
2014-07-09 00:48:35 +02:00
if group_table.column_exist('sepa_message'):
group = Group.__table__()
table = cls.__table__()
cursor.execute(*group.select(
group.id, group.sepa_message, group.company))
for group_id, message, company_id in cursor.fetchall():
cursor.execute(*table.insert(
[table.message, table.type, table.company,
table.origin, table.state],
[[message, 'out', company_id,
'account.payment.group,%s' % group_id,
'done']]))
group_table.drop_column('sepa_message')
@staticmethod
def default_type():
return 'in'
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_state():
return 'draft'
def get_filename(self, name):
pool = Pool()
Group = pool.get('account.payment.group')
if isinstance(self.origin, Group):
return self.origin.rec_name + '.xml'
@staticmethod
def _get_origin():
'Return list of Model names for origin Reference'
return ['account.payment.group']
@classmethod
def get_origin(cls):
IrModel = Pool().get('ir.model')
models = cls._get_origin()
models = IrModel.search([
('model', 'in', models),
])
return [(None, '')] + [(m.model, m.name) for m in models]
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, messages):
pass
@classmethod
@ModelView.button
@Workflow.transition('waiting')
def wait(cls, messages):
pass
@classmethod
@ModelView.button
@Workflow.transition('done')
def do(cls, messages):
2014-08-04 16:47:44 +02:00
for message in messages:
if message.type == 'in':
message.parse()
else:
message.send()
2014-07-09 00:48:35 +02:00
@classmethod
@ModelView.button
@Workflow.transition('canceled')
def cancel(cls, messages):
pass
2014-08-04 16:47:44 +02:00
@staticmethod
def _get_handlers():
pool = Pool()
Payment = pool.get('account.payment')
return {
'urn:iso:std:iso:20022:tech:xsd:camt.054.001.01':
lambda f: CAMT054(f, Payment),
'urn:iso:std:iso:20022:tech:xsd:camt.054.001.02':
lambda f: CAMT054(f, Payment),
'urn:iso:std:iso:20022:tech:xsd:camt.054.001.03':
lambda f: CAMT054(f, Payment),
'urn:iso:std:iso:20022:tech:xsd:camt.054.001.04':
lambda f: CAMT054(f, Payment),
}
@staticmethod
def get_namespace(message):
f = BytesIO(message)
for _, element in etree.iterparse(f, events=('start',)):
tag = etree.QName(element)
if tag.localname == 'Document':
return tag.namespace
def parse(self):
message = self.message.encode('utf-8')
f = BytesIO(message)
namespace = self.get_namespace(message)
handlers = self._get_handlers()
if namespace not in handlers:
raise # TODO UserError
handlers[namespace](f)
def send(self):
pass