add ebics modules

This commit is contained in:
Luc De Meyer 2018-08-14 16:35:15 +02:00
parent db508b8b4a
commit 2f06af7cf9
37 changed files with 2453 additions and 0 deletions

69
account_ebics/README.rst Normal file
View File

@ -0,0 +1,69 @@
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3
======================
EBICS banking protocol
======================
Implementation of the EBICS banking protocol.
This module facilitates the exchange of files with banks via the EBICS protocol.
Installation
============
The module depends upon
- https://pypi.python.org/pypi/fintech
- https://pypi.python.org/pypi/cryptography
Remark:
The EBICS 'Test Mode' for uploading orders requires Fintech 4.3.4 or higher.
Fintech license
---------------
If you have a valid Fintech.ebics license, you should add the following
licensing parameters to the odoo server configuration file:
- fintech_register_name
The name of the licensee.
- fintech_register_keycode
The keycode of the licensed version.
- fintech_register_users
The licensed EBICS user ids. It must be a string or a list of user ids.
Configuration
=============
Go to **Settings > Users**
Add the users that are authorised to maintain the EBICS configuration to the 'EBICS Manager' Group.
Go to **Accounting > Configuration > Miscellaneous > EBICS > EBICS Configuration**
Configure your EBICS configuration according to the contract with your bank.
Usage
=====
Go to **Accounting > Bank and Cash > EBICS Processing**
EBICS Return Codes
------------------
During the processing of your EBICS upload/download, your bank may return an Error Code, e.g.
EBICS Functional Error:
EBICS_NO_DOWNLOAD_DATA_AVAILABLE (code: 90005)
A detailled explanation of the codes can be found on http://www.ebics.org.
You can also find this information in the doc folder of this module (file EBICS_Annex1_ReturnCodes).

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2017 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models
from . import wizard

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2018 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'EBICS banking protocol',
'version': '8.0.1.4.0',
'license': 'AGPL-3',
'author': 'Noviat',
'category': 'Accounting & Finance',
'depends': ['account'],
'data': [
'security/ebics_security.xml',
'security/ir.model.access.csv',
'data/ebics_file_format.xml',
'views/menuitem.xml',
'views/ebics_config.xml',
'views/ebics_file.xml',
'views/ebics_file_format.xml',
'wizard/ebics_change_passphrase.xml',
'wizard/ebics_xfer.xml',
],
'installable': True,
'application': True,
}

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<!--
File format tested with the following banks:
- GLS Gemeinschaftsbank (Germany)
-->
<record id="ebics_ff_camt_053_001_02_stm" model="ebics.file.format">
<field name="name">camt.053.001.02.stm</field>
<field name="type">down</field>
<field name="order_type">C53</field>
<field name="description">Bank Statement in Format camt.053</field>
<field name="suffix">c53.xml</field>
</record>
<!--
File format tested with the following banks:
- GLS Gemeinschaftsbank (Germany)
-->
<record id="ebics_ff_pain_001_001_03_sct" model="ebics.file.format">
<field name="name">pain.001.001.03.sct</field>
<field name="type">up</field>
<field name="order_type">CCT</field>
<field name="description">Payment Order in Format pain.001.001.03</field>
<field name="suffix">xml</field>
</record>
<record id="ebics_ff_pain_008_001_02_sdd" model="ebics.file.format">
<field name="name">pain.008.001.02.sdd</field>
<field name="type">up</field>
<field name="order_type">CDD</field>
<field name="description">Sepa Core Direct Debit Order in Format pain.008.001.02</field>
<field name="suffix">xml</field>
</record>
<record id="ebics_ff_pain_008_001_02_sbb" model="ebics.file.format">
<field name="name">pain.008.001.02.sbb</field>
<field name="type">up</field>
<field name="order_type">CDB</field>
<field name="description">Sepa Direct Debit (B2B) Order in Format pain.008.001.02</field>
<field name="suffix">xml</field>
</record>
<!--
File format tested with the following banks:
- Credit Suisse (Switzerland)
-->
<record id="ebics_ff_pain_001" model="ebics.file.format">
<field name="name">pain.001</field>
<field name="type">up</field>
<field name="order_type">XE2</field>
<field name="description">Payment Order in Format pain.001.001.03</field>
<field name="suffix">xml</field>
</record>
<!--
File format tested with the following banks:
- Credit Suisse (Switzerland)
-->
<record id="ebics_ff_pain_008" model="ebics.file.format">
<field name="name">pain.008</field>
<field name="type">up</field>
<field name="order_type">XE3</field>
<field name="description">Direct Debit Order in Format pain.008.001.02</field>
<field name="suffix">xml</field>
</record>
<!--
File format tested with the following banks:
- CIC (France)
-->
<record id="ebics_ff_camt_xxx_cfonb120_stm" model="ebics.file.format">
<field name="name">camt.xxx.cfonb120.stm</field>
<field name="type">down</field>
<field name="order_type">FDL</field>
<field name="description">Bank Statement in Format cfonb120</field>
<field name="suffix">cfonb120.dat</field>
</record>
<!--
File format tested with the following banks:
- CIC (France)
-->
<record id="ebics_ff_pain_001_001_02_sct" model="ebics.file.format">
<field name="name">pain.001.001.02.sct</field>
<field name="type">up</field>
<field name="order_type">FUL</field>
<field name="description">Payment Order in Format pain.001.001.02</field>
<field name="suffix">xml</field>
</record>
</data>
</openerp>

Binary file not shown.

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
from . import fintech_ebics_register
from . import account_bank_statement
from . import ebics_config
from . import ebics_file
from . import ebics_file_format

View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2017 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import fields, models
class AccountBankStatement(models.Model):
_inherit = 'account.bank.statement'
ebics_file_id = fields.Many2one(
comodel_name='ebics.file', string='EBICS Data File')

View File

@ -0,0 +1,494 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2018 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
"""
import logging
logging.basicConfig(
level=logging.DEBUG,
format='[%(asctime)s] %(levelname)s - %(name)s: %(message)s')
"""
import base64
import logging
import re
import os
from sys import exc_info
from urllib2 import URLError
from openerp import api, fields, models, _
from openerp.exceptions import Warning as UserError
_logger = logging.getLogger(__name__)
try:
import fintech
from fintech.ebics import EbicsKeyRing, EbicsBank, EbicsUser,\
EbicsClient, EbicsFunctionalError, EbicsTechnicalError
fintech.cryptolib = 'cryptography'
except ImportError:
EbicsBank = object
_logger.warning('Failed to import fintech')
class EbicsBank(EbicsBank):
def _next_order_id(self, partnerid):
"""
EBICS protocol version H003 requires generation of the OrderID.
The OrderID must be a string between 'A000' and 'ZZZZ' and
unique for each partner id.
"""
return hasattr(self, '_order_number') and self._order_number or 'A000'
class EbicsConfig(models.Model):
"""
EBICS configuration is stored in a separate object in order to
allow extra security policies on this object.
Remark:
This Configuration model implements a simple model of the relationship
between users and authorizations and may need to be adapted
in next versions of this module to cope with higher complexity .
"""
_name = 'ebics.config'
_description = 'EBICS Configuration'
_order = 'name'
name = fields.Char(string='Name', required=True)
bank_id = fields.Many2one(
comodel_name='res.partner.bank',
readonly=True, states={'draft': [('readonly', False)]},
string='Bank Account', required=True)
ebics_host = fields.Char(
string='EBICS HostID', required=True,
readonly=True, states={'draft': [('readonly', False)]},
help="Contact your bank to get the EBICS HostID."
"\nIn France the BIC is usually allocated to the HostID "
"whereas in Germany it tends to be an institute specific string "
"of 8 characters.")
ebics_url = fields.Char(
string='EBICS URL', required=True,
readonly=True, states={'draft': [('readonly', False)]},
help="Contact your bank to get the EBICS URL.")
ebics_version = fields.Selection(
selection=[('H003', 'H003 (2.4)'),
('H004', 'H004 (2.5)')],
string='EBICS protocol version',
readonly=True, states={'draft': [('readonly', False)]},
required=True, default='H004')
ebics_partner = fields.Char(
string='EBICS PartnerID', required=True,
readonly=True, states={'draft': [('readonly', False)]},
help="Organizational unit (company or individual) "
"that concludes a contract with the bank. "
"\nIn this contract it will be agreed which order types "
"(file formats) are used, which accounts are concerned, "
"which of the customer's users (subscribers) "
"communicate with the EBICS bank server and the authorisations "
"that these users will possess. "
"\nIt is identified by the PartnerID.")
ebics_user = fields.Char(
string='EBICS UserID', required=True,
readonly=True, states={'draft': [('readonly', False)]},
help="Human users or a technical system that is/are "
"assigned to a customer. "
"\nOn the EBICS bank server it is identified "
"by the combination of UserID and PartnerID. "
"The technical subscriber serves only for the data exchange "
"between customer and financial institution. "
"The human user also can authorise orders.")
# Currently only a singe signature class per user is supported
# Classes A and B are not yet supported.
signature_class = fields.Selection(
selection=[('E', 'Single signature'),
('T', 'Transport signature')],
string='Signature Class',
required=True, default='T',
readonly=True, states={'draft': [('readonly', False)]},
help="Default signature class."
"This default can be overriden for specific "
"EBICS transactions (cf. File Formats).")
ebics_files = fields.Char(
string='EBICS Files Root', required=True,
readonly=True, states={'draft': [('readonly', False)]},
default=lambda self: self._default_ebics_files(),
help="Root Directory for EBICS File Transfer Folders.")
# We store the EBICS keys in a separate directory in the file system.
# This directory requires special protection to reduce fraude.
ebics_keys = fields.Char(
string='EBICS Keys', required=True,
readonly=True, states={'draft': [('readonly', False)]},
default=lambda self: self._default_ebics_keys(),
help="File holding the EBICS Keys."
"\nSpecify the full path (directory + filename).")
ebics_keys_found = fields.Boolean(
compute='_compute_ebics_keys_found')
ebics_passphrase = fields.Char(
string='EBICS Passphrase')
ebics_key_version = fields.Selection(
selection=[('A005', 'A005 (RSASSA-PKCS1-v1_5)'),
('A006', 'A006 (RSASSA-PSS)')],
string='EBICS key version',
default='A006',
readonly=True, states={'draft': [('readonly', False)]},
help="The key version of the electronic signature.")
ebics_key_bitlength = fields.Integer(
string='EBICS key bitlength',
default=2048,
readonly=True, states={'draft': [('readonly', False)]},
help="The bit length of the generated keys. "
"\nThe value must be between 1536 and 4096.")
ebics_ini_letter = fields.Binary(
string='EBICS INI Letter', readonly=True,
help="INI-letter PDF document to be sent to your bank.")
ebics_ini_letter_fn = fields.Char(
string='INI-letter Filename', readonly=True)
ebics_public_bank_keys = fields.Binary(
string='EBICS Public Bank Keys', readonly=True,
help="EBICS Public Bank Keys to be checked for consistency.")
ebics_public_bank_keys_fn = fields.Char(
string='EBICS Public Bank Keys Filename', readonly=True)
# X.509 Distinguished Name attributes used to
# create self-signed X.509 certificates
ebics_key_x509 = fields.Boolean(
string='X509 support',
help="Set this flag in order to work with "
"self-signed X.509 certificates")
ebics_key_x509_dn_cn = fields.Char(
string='Common Name [CN]',
readonly=True, states={'draft': [('readonly', False)]},
)
ebics_key_x509_dn_o = fields.Char(
string='Organization Name [O]',
readonly=True, states={'draft': [('readonly', False)]},
)
ebics_key_x509_dn_ou = fields.Char(
string='Organizational Unit Name [OU]',
readonly=True, states={'draft': [('readonly', False)]},
)
ebics_key_x509_dn_c = fields.Char(
string='Country Name [C]',
readonly=True, states={'draft': [('readonly', False)]},
)
ebics_key_x509_dn_st = fields.Char(
string='State Or Province Name [ST]',
readonly=True, states={'draft': [('readonly', False)]},
)
ebics_key_x509_dn_l = fields.Char(
string='Locality Name [L]',
readonly=True, states={'draft': [('readonly', False)]},
)
ebics_key_x509_dn_e = fields.Char(
string='Email Address',
readonly=True, states={'draft': [('readonly', False)]},
)
ebics_file_format_ids = fields.Many2many(
comodel_name='ebics.file.format',
column1='config_id', column2='format_id',
string='EBICS File Formats',
readonly=True, states={'draft': [('readonly', False)]},
)
state = fields.Selection(
[('draft', 'Draft'),
('init', 'Initialisation'),
('get_bank_keys', 'Get Keys from Bank'),
('to_verify', 'Verification'),
('active', 'Active')],
string='State',
default='draft',
required=True, readonly=True)
order_number = fields.Char(
size=4, readonly=True, states={'draft': [('readonly', False)]},
help="Specify the number for the next order."
"\nThis number should match the following pattern : "
"[A-Z]{1}[A-Z0-9]{3}")
active = fields.Boolean(
string='Active', default=True)
company_id = fields.Many2one(
'res.company', string='Company',
default=lambda self: self.env.user.company_id,
required=True)
@api.model
def _default_ebics_files(self):
return '/'.join(['/home/odoo/ebics_files', self._cr.dbname])
@api.model
def _default_ebics_keys(self):
return '/'.join(['/etc/odoo/ebics_keys',
self._cr.dbname,
'mykeys'])
@api.multi
def _compute_ebics_keys_found(self):
for cfg in self:
if cfg.ebics_keys:
dirname = os.path.dirname(self.ebics_keys)
self.ebics_keys_found = os.path.exists(dirname)
@api.multi
@api.constrains('order_number')
def _check_order_number(self):
for cfg in self:
nbr = cfg.order_number
ok = True
if nbr:
if len(nbr) != 4:
ok = False
else:
pattern = re.compile("[A-Z]{1}[A-Z0-9]{3}")
if not pattern.match(nbr):
ok = False
if not ok:
raise UserError(_(
"Order Number should comply with the following pattern:"
"\n[A-Z]{1}[A-Z0-9]{3}"))
@api.multi
def unlink(self):
for ebics_config in self:
if ebics_config.state == 'active':
raise UserError(_(
"You cannot remove active EBICS congirations."))
return super(EbicsConfig, self).unlink()
@api.multi
def set_to_draft(self):
return self.write({'state': 'draft'})
@api.multi
def set_to_active(self):
return self.write({'state': 'active'})
@api.multi
def ebics_init_1(self):
"""
Initialization of bank keys - Step 1:
Create new keys and certificates for this user
"""
self.ensure_one()
self._check_ebics_files()
if self.state != 'draft':
raise UserError(
_("Set state to 'draft' before Bank Key (re)initialisation."))
try:
keyring = EbicsKeyRing(
keys=self.ebics_keys,
passphrase=self.ebics_passphrase or None)
bank = EbicsBank(
keyring=keyring, hostid=self.ebics_host, url=self.ebics_url)
user = EbicsUser(
keyring=keyring, partnerid=self.ebics_partner,
userid=self.ebics_user)
except:
exctype, value = exc_info()[:2]
error = _("EBICS Initialisation Error:")
error += '\n' + str(exctype) + '\n' + str(value)
raise UserError(error)
self._check_ebics_keys()
if not os.path.isfile(self.ebics_keys):
user.create_keys(
keyversion=self.ebics_key_version,
bitlength=self.ebics_key_bitlength)
if self.ebics_key_x509:
dn_attrs = {
'commonName': self.ebics_key_x509_dn_cn,
'organizationName': self.ebics_key_x509_dn_o,
'organizationalUnitName': self.ebics_key_x509_dn_ou,
'countryName': self.ebics_key_x509_dn_c,
'stateOrProvinceName': self.ebics_key_x509_dn_st,
'localityName': self.ebics_key_x509_dn_l,
'emailAddress': self.ebics_key_x509_dn_e,
}
kwargs = {k: v for k, v in dn_attrs.items() if v}
user.create_certificates(**kwargs)
client = EbicsClient(bank, user, version=self.ebics_version)
# Send the public electronic signature key to the bank.
try:
if self.ebics_version == 'H003':
bank._order_number = self._get_order_number()
OrderID = client.INI()
_logger.info(
'%s, EBICS INI command, OrderID=%s', self._name, OrderID)
if self.ebics_version == 'H003':
self._update_order_number(OrderID)
except URLError:
e = exc_info()
raise UserError(_(
"urlopen error:\n url '%s' - %s")
% (self.ebics_url, e[1].reason.strerror))
except EbicsFunctionalError:
e = exc_info()
error = _("EBICS Functional Error:")
error += '\n'
error += '%s (code: %s)' % (e[1].message, e[1].code)
raise UserError(error)
except EbicsTechnicalError:
e = exc_info()
error = _("EBICS Technical Error:")
error += '\n'
error += '%s (code: %s)' % (e[1].message, e[1].code)
raise UserError(error)
# Send the public authentication and encryption keys to the bank.
if self.ebics_version == 'H003':
bank._order_number = self._get_order_number()
OrderID = client.HIA()
_logger.info('%s, EBICS HIA command, OrderID=%s', self._name, OrderID)
if self.ebics_version == 'H003':
self._update_order_number(OrderID)
# Create an INI-letter which must be printed and sent to the bank.
lang = self.env.user.lang[:2]
cc = self.bank_id.country_id.code
if cc in ['FR', 'DE']:
lang = cc
tmp_dir = os.path.normpath(self.ebics_files + '/tmp')
if not os.path.isdir(tmp_dir):
os.makedirs(tmp_dir, 0700)
fn_date = fields.Date.today()
fn = '_'.join([self.ebics_host, 'ini_letter', fn_date]) + '.pdf'
full_tmp_fn = os.path.normpath(tmp_dir + '/' + fn)
user.create_ini_letter(
bankname=self.bank_id.bank.name,
path=full_tmp_fn,
lang=lang)
with open(full_tmp_fn, 'rb') as f:
letter = f.read()
self.write({
'ebics_ini_letter': base64.encodestring(letter),
'ebics_ini_letter_fn': fn,
})
return self.write({'state': 'init'})
@api.multi
def ebics_init_2(self):
"""
Initialization of bank keys - Step 2:
Activation of the account by the bank.
"""
if self.state != 'init':
raise UserError(
_("Set state to 'Initialisation'."))
self.ensure_one()
return self.write({'state': 'get_bank_keys'})
@api.multi
def ebics_init_3(self):
"""
Initialization of bank keys - Step 3:
After the account has been activated the public bank keys
must be downloaded and checked for consistency.
"""
self.ensure_one()
self._check_ebics_files()
if self.state != 'get_bank_keys':
raise UserError(
_("Set state to 'Get Keys from Bank'."))
keyring = EbicsKeyRing(
keys=self.ebics_keys, passphrase=self.ebics_passphrase)
bank = EbicsBank(
keyring=keyring, hostid=self.ebics_host, url=self.ebics_url)
user = EbicsUser(
keyring=keyring, partnerid=self.ebics_partner,
userid=self.ebics_user)
client = EbicsClient(
bank, user, version=self.ebics_version)
public_bank_keys = client.HPB()
tmp_dir = os.path.normpath(self.ebics_files + '/tmp')
if not os.path.isdir(tmp_dir):
os.makedirs(tmp_dir, 0700)
fn_date = fields.Date.today()
fn = '_'.join([self.ebics_host, 'public_bank_keys', fn_date]) + '.txt'
self.write({
'ebics_public_bank_keys': base64.encodestring(public_bank_keys),
'ebics_public_bank_keys_fn': fn,
'state': 'to_verify',
})
return True
@api.multi
def ebics_init_4(self):
"""
Initialization of bank keys - Step 2:
Confirm Verification of the public bank keys
and activate the bank keyu.
"""
self.ensure_one()
if self.state != 'to_verify':
raise UserError(
_("Set state to 'Verification'."))
keyring = EbicsKeyRing(
keys=self.ebics_keys, passphrase=self.ebics_passphrase)
bank = EbicsBank(
keyring=keyring, hostid=self.ebics_host, url=self.ebics_url)
bank.activate_keys()
return self.write({'state': 'active'})
@api.multi
def change_passphrase(self):
self.ensure_one()
ctx = dict(self._context, default_ebics_config_id=self.id)
module = __name__.split('addons.')[1].split('.')[0]
view = self.env.ref(
'%s.ebics_change_passphrase_view_form' % module)
return {
'name': _('EBICS keys change passphrase'),
'view_type': 'form',
'view_mode': 'form',
'res_model': 'ebics.change.passphrase',
'view_id': view.id,
'target': 'new',
'context': ctx,
'type': 'ir.actions.act_window',
}
def _get_order_number(self):
return self.order_number
def _update_order_number(self, OrderID):
o_list = list(OrderID)
for i, c in enumerate(reversed(o_list), start=1):
if c == '9':
o_list[-i] = 'A'
break
if c == 'Z':
continue
else:
o_list[-i] = chr(ord(c) + 1)
break
next = ''.join(o_list)
if next == 'ZZZZ':
next = 'A000'
self.order_number = next
def _check_ebics_keys(self):
if self.ebics_keys:
dirname = os.path.dirname(self.ebics_keys)
if not os.path.exists(dirname):
raise UserError(_(
"EBICS Keys Directory '%s' is not available."
"\nPlease contact your system administrator.")
% dirname)
def _check_ebics_files(self):
dirname = self.ebics_files or ''
if not os.path.exists(dirname):
raise UserError(_(
"EBICS Files Root Directory %s is not available."
"\nPlease contact your system administrator.")
% dirname)

View File

@ -0,0 +1,256 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2018 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from openerp import api, fields, models, _
from openerp.exceptions import Warning as UserError
_logger = logging.getLogger(__name__)
class EbicsFile(models.Model):
_name = 'ebics.file'
_description = 'Object to store EBICS Data Files'
_order = 'date desc'
name = fields.Char(string='Filename')
data = fields.Binary(string='File', readonly=True)
format_id = fields.Many2one(
comodel_name='ebics.file.format',
string='EBICS File Formats',
readonly=True)
type = fields.Selection(
related='format_id.type',
readonly=True)
date_from = fields.Date(
readonly=True,
help="'Date From' as entered in the download wizard.")
date_to = fields.Date(
readonly=True,
help="'Date To' as entered in the download wizard.")
date = fields.Datetime(
required=True, readonly=True,
help='File Upload/Download date')
bank_statement_ids = fields.One2many(
comodel_name='account.bank.statement',
inverse_name='ebics_file_id',
string='Generated Bank Statements', readonly=True)
state = fields.Selection(
[('draft', 'Draft'),
('done', 'Done')],
string='State',
default='draft',
required=True, readonly=True)
user_id = fields.Many2one(
comodel_name='res.users', string='User',
default=lambda self: self.env.user,
readonly=True)
note = fields.Text(string='Notes')
note_process = fields.Text(string='Notes')
company_id = fields.Many2one(
comodel_name='res.company',
string='Company',
default=lambda self: self._default_company_id())
_sql_constraints = [
('name_company_uniq', 'unique (name, company_id, format_id)',
'This File has already been imported !')
]
@api.model
def _default_company_id(self):
"""
Adapt this method in case your bank provides transactions
of multiple legal entities in a single EBICS File.
"""
return self.env.user.company_id
@api.multi
def unlink(self):
ff_methods = self._file_format_methods()
for ebics_file in self:
if ebics_file.state == 'done':
raise UserError(_(
"You can only remove EBICS files in state 'Draft'."))
# execute format specific actions
ff = ebics_file.format_id.name
if ff in ff_methods:
if ff_methods[ff].get('unlink'):
ff_methods[ff]['unlink'](ebics_file)
# remove bank statements
ebics_file.bank_statement_ids.unlink()
return super(EbicsFile, self).unlink()
@api.multi
def set_to_draft(self):
return self.write({'state': 'draft'})
@api.multi
def set_to_done(self):
return self.write({'state': 'done'})
@api.multi
def process(self):
self.ensure_one()
self.note_process = ''
ff_methods = self._file_format_methods()
ff = self.format_id.name
if ff in ff_methods:
if ff_methods[ff].get('process'):
res = ff_methods[ff]['process'](self)
self.state = 'done'
return res
else:
return self._process_undefined_format()
@api.multi
def action_open_bank_statements(self):
self.ensure_one()
action = self.env['ir.actions.act_window'].for_xml_id(
'account', 'action_bank_statement_tree')
domain = eval(action.get('domain') or '[]')
domain += [('id', 'in', self._context.get('statement_ids'))]
action.update({'domain': domain})
return action
@api.multi
def button_close(self):
self.ensure_one()
return {'type': 'ir.actions.act_window_close'}
def _file_format_methods(self):
"""
Extend this dictionary in order to add support
for extra file formats.
"""
res = {
'camt.xxx.cfonb120.stm':
{'process': self._process_cfonb120,
'unlink': self._unlink_cfonb120},
'camt.053.001.02.stm':
{'process': self._process_camt053,
'unlink': self._unlink_camt053},
}
return res
def _check_import_module(self, module):
mod = self.env['ir.module.module'].search(
[('name', '=', module),
('state', '=', 'installed')])
if not mod:
raise UserError(_(
"The module to process the '%s' format is not installed "
"on your system. "
"\nPlease install module '%s'")
% (self.format_id.name, module))
def _process_result_action(self, ctx):
module = __name__.split('addons.')[1].split('.')[0]
result_view = self.env.ref('%s.ebics_file_view_form_result' % module)
return {
'name': _('Import EBICS File'),
'res_id': self.id,
'view_type': 'form',
'view_mode': 'form',
'res_model': self._name,
'view_id': result_view.id,
'target': 'new',
'context': ctx,
'type': 'ir.actions.act_window',
}
@staticmethod
def _process_cfonb120(self):
"""
TODO:
adapt OCA import logic to find correct journal on the basis
of both account number and currency.
Prompt for journal in case the journal is not found.
"""
import_module = 'account_bank_statement_import_fr_cfonb'
self._check_import_module(import_module)
wiz_model = 'account.bank.statement.import'
wiz_vals = {'data_file': self.data}
wiz = self.env[wiz_model].create(wiz_vals)
res = wiz.import_file()
notifications = []
statement_ids = []
if res.get('context'):
notifications = res['context'].get('notifications', [])
statement_ids = res['context'].get('statement_ids', [])
if notifications:
for notif in notifications:
parts = []
for k in ['type', 'message', 'details']:
if notif.get(k):
msg = '%s: %s' % (k, notif[k])
parts.append(msg)
self.note_process += '\n'.join(parts)
self.note_process += '\n'
self.note_process += '\n'
self.note_process += _(
"Number of Bank Statements: %s"
) % len(statement_ids)
if statement_ids:
self.bank_statement_ids = [(6, 0, statement_ids)]
ctx = dict(self._context, statement_ids=statement_ids)
return self._process_result_action(ctx)
@staticmethod
def _unlink_cfonb120(self):
"""
Placeholder for cfonb120 specific actions before removing the
EBICS data file and its related bank statements.
"""
pass
@staticmethod
def _process_camt053(self):
import_module = 'account_bank_statement_import_camt'
self._check_import_module(import_module)
wiz_model = 'account.bank.statement.import'
wiz_vals = {
'data_file': self.data,
# 'filename': self.name, # V10 field
}
wiz = self.env[wiz_model].create(wiz_vals)
res = wiz.import_file()
notifications = []
statement_ids = []
if res.get('context'):
notifications = res['context'].get('notifications', [])
statement_ids = res['context'].get('statement_ids', [])
if notifications:
for notif in notifications:
parts = []
for k in ['type', 'message', 'details']:
if notif.get(k):
msg = '%s: %s' % (k, notif[k])
parts.append(msg)
self.note_process += '\n'.join(parts)
self.note_process += '\n'
self.note_process += '\n'
self.note_process += _(
"Number of Bank Statements: %s"
) % len(statement_ids)
if statement_ids:
self.bank_statement_ids = [(6, 0, statement_ids)]
ctx = dict(self._context, statement_ids=statement_ids)
return self._process_result_action(ctx)
@staticmethod
def _unlink_camt053(self):
"""
Placeholder for camt053 specific actions before removing the
EBICS data file and its related bank statements.
"""
pass
def _process_undefined_format(self):
raise UserError(_(
"The current version of the 'account_ebics' module "
"has no support to automatically process EBICS files "
"with format %s."
) % self.format_id.name)

View File

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2018 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import api, fields, models
class EbicsFileFormat(models.Model):
_name = 'ebics.file.format'
_description = 'EBICS File Formats'
_order = 'type,name'
name = fields.Selection(
selection=lambda self: self._selection_name(),
string='Request Type', required=True)
type = fields.Selection(
selection=[('down', 'Download'),
('up', 'Upload')],
required=True)
order_type = fields.Selection(
selection=lambda self: self._selection_order_type(),
string='Order Type',
help="For most banks is France you should use the "
"format neutral Order Types 'FUL' for upload "
"and 'FDL' for download.")
signature_class = fields.Selection(
selection=[('E', 'Single signature'),
('T', 'Transport signature')],
string='Signature Class',
help="Please doublecheck the security of your Odoo "
"ERP system when using class 'E' to prevent unauthorised "
"users to make supplier payments."
"\nLeave this field empty to use the default "
"defined for your bank connection.")
description = fields.Char()
suffix = fields.Char(
required=True,
help="Specify the filename suffix for this File Format."
"\nE.g. camt.053.xml")
@api.model
def _selection_order_type(self):
up = self._supported_upload_order_types()
down = self._supported_download_order_types()
selection = [(x, x) for x in up + down]
return selection
def _supported_upload_order_types(self):
return ['FUL', 'CCT', 'CDD', 'CDB', 'XE2', 'XE3']
def _supported_download_order_types(self):
return ['FDL', 'C53']
@api.model
def _selection_name(self):
"""
List of supported EBICS Request Types.
Extend this method via a custom module when testing
a new Request Type and make a PR for the
account_ebics module when this new Request Type
is working correctly.
This PR should include at least updates to
- 'data/ebics_file_format.xml'
- 'models/ebics_file_format.py'
An overview of the EBICS Request Types can be found in
the doc folder of this module (EBICS_Annex2).
"""
request_types = [
'camt.053.001.02.stm',
'pain.001.001.03.sct',
'pain.008.001.02.sdd',
'pain.008.001.02.sbb',
'camt.xxx.cfonb120.stm',
'pain.001.001.02.sct',
'camt.053',
'pain.001',
'pain.008',
]
selection = [(x, x) for x in request_types]
return selection

View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2018 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from sys import exc_info
from traceback import format_exception
from openerp.tools import config
_logger = logging.getLogger(__name__)
try:
import fintech
except ImportError:
fintech = None
_logger.warning('Failed to import fintech')
fintech_register_name = config.get('fintech_register_name')
fintech_register_keycode = config.get('fintech_register_keycode')
fintech_register_users = config.get('fintech_register_users')
try:
if fintech:
fintech.cryptolib = 'cryptography'
fintech.register(
fintech_register_name,
fintech_register_keycode,
fintech_register_users)
except RuntimeError, e:
if e.message == "'register' can be called only once":
pass
else:
_logger.error(e.message)
fintech.register()
except:
msg = "fintech.register error"
tb = ''.join(format_exception(*exc_info()))
msg += '\n%s' % tb
_logger.error(msg)
fintech.register()

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="res.groups" id="group_ebics_manager">
<field name="name">EBICS Manager</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
</data>
<data noupdate="1">
<record id="ebics_config_comp_rule" model="ir.rule">
<field name="name">EBICS Configuration model company rule</field>
<field name="model_id" ref="model_ebics_config"/>
<field eval="True" name="global"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
<record id="ebics_file_comp_rule" model="ir.rule">
<field name="name">EBICS File model company rule</field>
<field name="model_id" ref="model_ebics_file"/>
<field eval="True" name="global"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ebics_config_manager,ebics_config manager,model_ebics_config,account_ebics.group_ebics_manager,1,1,1,1
access_ebics_config_user,ebics_config user,model_ebics_config,account.group_account_user,1,0,0,0
access_ebics_file_format_manager,ebics_file_format manager,model_ebics_file_format,account_ebics.group_ebics_manager,1,1,1,1
access_ebics_file_format_user,ebics_file_format user,model_ebics_file_format,account.group_account_user,1,0,0,0
access_ebics_file_manager,ebics_file manager,model_ebics_file,account_ebics.group_ebics_manager,1,1,1,1
access_ebics_file_user,ebics_file user,model_ebics_file,account.group_account_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_ebics_config_manager ebics_config manager model_ebics_config account_ebics.group_ebics_manager 1 1 1 1
3 access_ebics_config_user ebics_config user model_ebics_config account.group_account_user 1 0 0 0
4 access_ebics_file_format_manager ebics_file_format manager model_ebics_file_format account_ebics.group_ebics_manager 1 1 1 1
5 access_ebics_file_format_user ebics_file_format user model_ebics_file_format account.group_account_user 1 0 0 0
6 access_ebics_file_manager ebics_file manager model_ebics_file account_ebics.group_ebics_manager 1 1 1 1
7 access_ebics_file_user ebics_file user model_ebics_file account.group_account_user 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,123 @@
<?xml version="1.0" ?>
<openerp>
<data>
<record id="ebics_config_view_tree" model="ir.ui.view">
<field name="name">ebics.config.tree</field>
<field name="model">ebics.config</field>
<field name="arch" type="xml">
<tree string="EBICS Configuration" colors="blue:state!='active'">
<field name="name"/>
<field name="bank_id"/>
<field name="ebics_host"/>
<field name="ebics_user"/>
<field name="state"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<record id="ebics_config_view_form" model="ir.ui.view">
<field name="name">ebics.config.form</field>
<field name="model">ebics.config</field>
<field name="arch" type="xml">
<form string="EBICS Configuration">
<header>
<button name="set_to_draft" states="active" string="Set to Draft" type="object"
groups="account.group_account_manager"
help="Set to Draft in order to reinitialize your bank connection."/>
<button name="set_to_active" states="draft" string="Force Active" type="object"
groups="account.group_account_manager"
help="Use this button to bypass the EBICS initialization (e.g. in case you have manually transferred active EBICS keys from another system."/>
<field name="state" widget="statusbar"/>
</header>
<group col="4">
<field name="name" colspan="2"/>
<field name="bank_id" domain="[('company_id', '=', company_id)]"/>
<field name="ebics_host"/>
<field name="ebics_url"/>
<field name="ebics_partner"/>
<field name="ebics_user"/>
<field name="ebics_version"/>
<field name="signature_class"/>
<field name="ebics_files"/>
<field name="order_number"
attrs="{'invisible': [('ebics_version', '=', 'H004')]}"/>
<field name="active"/>
<field name="company_id" widget='selection' groups="base.group_multi_company"/>
</group>
<notebook>
<page string="Keys" groups="account_ebics.group_ebics_manager">
<header>
<button name="ebics_init_1" states="draft" string="EBICS Initialisation" type="object" class="oe_highlight"
groups="account.group_account_manager"
help="Initialise EBICS Bank Keys"/>
<button name="ebics_init_2" states="init" string="Account activated" type="object" class="oe_highlight"
groups="account.group_account_manager"
help="EBICS Initialisation - Push this button when the account has been activated by the bank."/>
<button name="ebics_init_3" states="get_bank_keys" string="Get Bank Keys" type="object" class="oe_highlight"
groups="account.group_account_manager"
help="EBICS Initialisation - After the account has been activated the public bank keys must be downloaded and checked for consistency."/>
<button name="ebics_init_4" states="to_verify" string="Bank Keys Verified" type="object" class="oe_highlight"
groups="account.group_account_manager"
help="EBICS Initialisation - Push this button when the public have been checked for consistency."/>
<button name="change_passphrase" string="Change Passphrase" type="object" class="oe_highlight"
attrs="{'invisible': [('ebics_keys_found', '=', False)]}"
groups="account.group_account_manager"/>
</header>
<group col="4" name="ebics_key">
<field name="ebics_keys"/>
<field name="ebics_keys_found" invisible="1"/>
<field name="ebics_passphrase" password="True" attrs="{'invisible': [('ebics_keys_found', '!=', False)]}"/>
<newline/>
<field name="ebics_key_version"/>
<field name="ebics_key_bitlength"/>
<field name="ebics_key_x509"/>
</group>
<group colspan="4" name="ebics_ini_letter" attrs="{'invisible': [('ebics_ini_letter', '=', False)]}">
<field name="ebics_ini_letter_fn" invisible="1"/>
<field name="ebics_ini_letter" filename="ebics_ini_letter_fn"/>
</group>
<group colspan="4" name="ebics_public_bank_keys" attrs="{'invisible': [('ebics_public_bank_keys', '=', False)]}">
<field name="ebics_public_bank_keys_fn" invisible="1"/>
<field name="ebics_public_bank_keys" filename="ebics_public_bank_keys_fn"/>
</group>
<group col="4" name="dn" attrs="{'invisible': [('ebics_key_x509', '=', False)]}">
<label string="Distinguished Name attributes used to create self-signed X.509 certificates:" colspan="4"/>
<group name="dn_l" colspan="2">
<field name="ebics_key_x509_dn_cn"/>
<field name="ebics_key_x509_dn_o"/>
<field name="ebics_key_x509_dn_l"/>
<field name="ebics_key_x509_dn_c"/>
</group>
<group name="dn_r" colspan="2">
<field name="ebics_key_x509_dn_e"/>
<field name="ebics_key_x509_dn_ou"/>
<field name="ebics_key_x509_dn_st"/>
</group>
</group>
</page>
<page string="File Formats" groups="account_ebics.group_ebics_manager">
<field name="ebics_file_format_ids"/>
</page>
</notebook>
</form>
</field>
</record>
<record id="ebics_config_action" model="ir.actions.act_window">
<field name="name">EBICS Configuration</field>
<field name="res_model">ebics.config</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="ebics_config_menu"
name="EBICS Configuration"
parent="ebics_menu"
action="ebics_config_action"
groups="account_ebics.group_ebics_manager"
sequence="10"/>
</data>
</openerp>

View File

@ -0,0 +1,218 @@
<?xml version="1.0" ?>
<openerp>
<data>
<menuitem id="ebics_file_menu"
name="EBICS Files"
parent="ebics_processing_menu"
sequence="30"/>
<record id="ebics_file_view_search" model="ir.ui.view">
<field name="name">ebics.file.search</field>
<field name="model">ebics.file</field>
<field name="arch" type="xml">
<search string="Search EBICS Files">
<group col="10" colspan="4">
<field name="date_from"/>
<field name="date_to"/>
<field name="name"/>
<field name="format_id"/>
<field name="user_id"/>
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
</group>
<newline/>
<group expand="0" string="Group By">
<filter string="File Format" context="{'group_by':'format_id'}"/>
<filter string="State" context="{'group_by':'state'}"/>
<filter string="User" context="{'group_by':'user_id'}"/>
<filter string="Company" domain="[]" groups="base.group_multi_company" context="{'group_by':'company_id'}"/>
</group>
</search>
</field>
</record>
<!-- Download -->
<record id="ebics_file_view_tree_download" model="ir.ui.view">
<field name="name">ebics.file.tree</field>
<field name="model">ebics.file</field>
<field name="arch" type="xml">
<tree string="EBICS Files" colors="blue:state=='draft'" create="false">
<field name="date" string="Download Date"/>
<field name="name"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="user_id"/>
<field name="state"/>
<field name="format_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<record id="ebics_file_view_form_download" model="ir.ui.view">
<field name="name">ebics.file.form</field>
<field name="model">ebics.file</field>
<field name="priority">1</field>
<field name="arch" type="xml">
<form string="EBICS File" create="false">
<header>
<button name="set_to_draft" states="done" string="Set to Draft" type="object" groups="account.group_account_manager"/>
<button name="process"
class="oe_highlight"
states="draft"
string="Process"
type="object"
groups="account.group_account_manager"
help="Process the EBICS File"/>
<field name="state" widget="statusbar"/>
</header>
<group colspan="4" col="4">
<field name="date" string="Download Date"/>
<field name="name"/>
<field name="data" filename="name"/>
<field name="format_id"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="user_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<notebook>
<page string="Additional Information">
<field name="note" nolabel="1"/>
</page>
<page string="Bank Statements" attrs="{'invisible':[('bank_statement_ids','=',[])]}">
<field name="bank_statement_ids" nolabel="1"/>
</page>
</notebook>
</form>
</field>
</record>
<record id="ebics_file_view_form_result" model="ir.ui.view">
<field name="name">ebics.file.process.result</field>
<field name="model">ebics.file</field>
<field name="priority">2</field>
<field name="arch" type="xml">
<form string="Process EBICS File">
<separator colspan="4" string="Results :" />
<field name="note_process" colspan="4" nolabel="1" width="850" height="400"/>
<footer>
<button name="action_open_bank_statements" string="View Bank Statement(s)"
type="object" class="oe_highlight"
invisible="not context.get('statement_ids')"/>
<button name="button_close" type="object" string="Close"/>
</footer>
</form>
</field>
</record>
<record id="ebics_file_action_download" model="ir.actions.act_window">
<field name="name">EBICS Download Files</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">ebics.file</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" eval="False"/>
<field name="domain">[('type','=','down')]</field>
<field name="search_view_id" ref="ebics_file_view_search"/>
</record>
<record id="ebics_file_action_download_tree" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="ebics_file_view_tree_download"/>
<field name="act_window_id" ref="ebics_file_action_download"/>
</record>
<record id="ebics_file_action_download_form" model="ir.actions.act_window.view">
<field eval="2" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="ebics_file_view_form_download"/>
<field name="act_window_id" ref="ebics_file_action_download"/>
</record>
<menuitem id="ebics_file_menu_download"
name="Download"
parent="ebics_file_menu"
action="ebics_file_action_download"
sequence="31"/>
<!-- Upload -->
<record id="ebics_file_view_tree_upload" model="ir.ui.view">
<field name="name">ebics.file.tree</field>
<field name="model">ebics.file</field>
<field name="arch" type="xml">
<tree string="EBICS Files" colors="blue:state=='draft'" create="false">
<field name="date" string="Upload Date"/>
<field name="name"/>
<field name="user_id"/>
<field name="state"/>
<field name="format_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<record id="ebics_file_view_form_upload" model="ir.ui.view">
<field name="name">ebics.file.form</field>
<field name="model">ebics.file</field>
<field name="priority">1</field>
<field name="arch" type="xml">
<form string="EBICS File" create="false">
<header>
<button name="set_to_draft" states="done" string="Set to Draft" type="object" groups="account.group_account_manager"/>
<button name="set_to_done" states="draft" string="Set to Done" type="object" groups="account.group_account_manager"/>
<field name="state" widget="statusbar"/>
</header>
<group colspan="4" col="4">
<field name="date" string="Upload Date"/>
<field name="name"/>
<field name="data" filename="name"/>
<field name="format_id"/>
<field name="user_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<notebook>
<page string="Additional Information">
<field name="note" nolabel="1"/>
</page>
</notebook>
</form>
</field>
</record>
<record id="ebics_file_action_upload" model="ir.actions.act_window">
<field name="name">EBICS Upload Files</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">ebics.file</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" eval="False"/>
<field name="domain">[('type','=','up')]</field>
<field name="search_view_id" ref="ebics_file_view_search"/>
</record>
<record id="ebics_file_action_upload_tree" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="ebics_file_view_tree_upload"/>
<field name="act_window_id" ref="ebics_file_action_upload"/>
</record>
<record id="ebics_file_action_upload_form" model="ir.actions.act_window.view">
<field eval="2" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="ebics_file_view_form_upload"/>
<field name="act_window_id" ref="ebics_file_action_upload"/>
</record>
<menuitem id="ebics_file_menu_upload"
name="Upload"
parent="ebics_file_menu"
action="ebics_file_action_upload"
sequence="31"/>
</data>
</openerp>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" ?>
<openerp>
<data>
<record id="ebics_file_format_view_tree" model="ir.ui.view">
<field name="name">ebics.file.format.tree</field>
<field name="model">ebics.file.format</field>
<field name="arch" type="xml">
<tree string="EBICS File Formats">
<field name="type"/>
<field name="order_type"/>
<field name="signature_class"/>
<field name="name"/>
<field name="description"/>
</tree>
</field>
</record>
<record id="ebics_file_format_view_form" model="ir.ui.view">
<field name="name">ebics.file.format.form</field>
<field name="model">ebics.file.format</field>
<field name="arch" type="xml">
<form string="EBICS File Format">
<group col="4">
<field name="type"/>
<field name="order_type"/>
<field name="name"/>
<field name="signature_class"/>
<field name="suffix"/>
<newline/>
<field name="description"/>
</group>
</form>
</field>
</record>
<record id="ebics_file_format_action" model="ir.actions.act_window">
<field name="name">EBICS File Formats</field>
<field name="res_model">ebics.file.format</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="ebics_file_format_menu"
name="EBICS File Formats"
parent="ebics_menu"
action="ebics_file_format_action"
groups="account_ebics.group_ebics_manager"
sequence="20"/>
</data>
</openerp>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" ?>
<openerp>
<data>
<menuitem id="ebics_processing_menu"
name="EBICS Processing"
parent="account.menu_finance_bank_and_cash"
sequence="40"/>
<menuitem id="ebics_menu"
name="EBICS"
parent="account.menu_configuration_misc"
groups="account_ebics.group_ebics_manager"
sequence="100"/>
</data>
</openerp>

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import ebics_change_passphrase
from . import ebics_xfer

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2018 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from openerp import api, fields, models, _
from openerp.exceptions import Warning as UserError
_logger = logging.getLogger(__name__)
try:
import fintech
from fintech.ebics import EbicsKeyRing
fintech.cryptolib = 'cryptography'
except ImportError:
_logger.warning('Failed to import fintech')
class EbicsChangePassphrase(models.TransientModel):
_name = 'ebics.change.passphrase'
_description = 'Change EBICS keys passphrase'
ebics_config_id = fields.Many2one(
comodel_name='ebics.config',
string='EBICS Configuration',
readonly=True)
old_pass = fields.Char(
string='Old Passphrase',
required=True)
new_pass = fields.Char(
string='New Passphrase',
required=True)
new_pass_check = fields.Char(
string='New Passphrase (verification)',
required=True)
note = fields.Text(string='Notes', readonly=True)
@api.multi
def change_passphrase(self):
self.ensure_one()
if self.old_pass != self.ebics_config_id.ebics_passphrase:
raise UserError(_(
"Incorrect old passphrase."))
if self.new_pass != self.new_pass_check:
raise UserError(_(
"New passphrase verification error."))
if self.new_pass == self.ebics_config_id.ebics_passphrase:
raise UserError(_(
"New passphrase equal to old passphrase."))
try:
keyring = EbicsKeyRing(
keys=self.ebics_config_id.ebics_keys,
passphrase=self.ebics_config_id.ebics_passphrase)
keyring.change_passphrase(self.new_pass)
except ValueError, e:
raise UserError(str(e))
self.ebics_config_id.ebics_passphrase = self.new_pass
self.note = "The EBICS Passphrase has been changed."
module = __name__.split('addons.')[1].split('.')[0]
result_view = self.env.ref(
'%s.ebics_change_passphrase_view_form_result' % module)
return {
'name': _('EBICS Keys Change Passphrase'),
'res_id': self.id,
'view_type': 'form',
'view_mode': 'form',
'res_model': 'ebics.change.passphrase',
'view_id': result_view.id,
'target': 'new',
'type': 'ir.actions.act_window',
}
@api.multi
def button_close(self):
self.ensure_one()
return {'type': 'ir.actions.act_window_close'}

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="ebics_change_passphrase_view_form" model="ir.ui.view">
<field name="name">EBICS Keys Change Passphrase</field>
<field name="model">ebics.change.passphrase</field>
<field name="priority">1</field>
<field name="arch" type="xml">
<form string="EBICS Keys Change Passphrase">
<group>
<field name="old_pass" password="True"/>
<field name="new_pass" password="True"/>
<field name="new_pass_check" password="True"/>
</group>
<footer>
<button name="change_passphrase" string="Change Passphrase" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="ebics_change_passphrase_view_form_result" model="ir.ui.view">
<field name="name">EBICS Keys Change Passphrase</field>
<field name="model">ebics.change.passphrase</field>
<field name="priority">2</field>
<field name="arch" type="xml">
<form string="EBICS Keys Change Passphrase">
<separator colspan="4" string="Results :" />
<field name="note" colspan="4" nolabel="1" width="850" height="400"/>
<footer>
<button name="button_close" type="object" string="Close"/>
</footer>
</form>
</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,542 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2018 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
"""
import logging
logging.basicConfig(
level=logging.DEBUG,
format='[%(asctime)s] %(levelname)s - %(name)s: %(message)s')
"""
import base64
import logging
import os
from sys import exc_info
from traceback import format_exception
from openerp import api, fields, models, _
from openerp.exceptions import Warning as UserError
_logger = logging.getLogger(__name__)
try:
import fintech
from fintech.ebics import EbicsKeyRing, EbicsBank, EbicsUser, EbicsClient,\
EbicsFunctionalError, EbicsTechnicalError, EbicsVerificationError
fintech.cryptolib = 'cryptography'
except ImportError:
EbicsBank = object
_logger.warning('Failed to import fintech')
class EbicsBank(EbicsBank):
def _next_order_id(self, partnerid):
"""
EBICS protocol version H003 requires generation of the OrderID.
The OrderID must be a string between 'A000' and 'ZZZZ' and
unique for each partner id.
"""
return hasattr(self, '_order_number') and self._order_number or 'A000'
class EbicsXfer(models.TransientModel):
_name = 'ebics.xfer'
_description = 'EBICS file transfer'
ebics_config_id = fields.Many2one(
comodel_name='ebics.config',
string='EBICS Configuration',
domain=[('state', '=', 'active')],
required=True,
default=lambda self: self._default_ebics_config_id())
ebics_passphrase = fields.Char(
string='EBICS Passphrase')
date_from = fields.Date()
date_to = fields.Date()
upload_data = fields.Binary(string='File to Upload')
upload_fname = fields.Char(
string='Upload Filename', default='')
upload_fname_dummy = fields.Char(
related='upload_fname', string='Upload Filename', readonly=True)
format_id = fields.Many2one(
comodel_name='ebics.file.format',
string='EBICS File Format',
help="Select EBICS File Format to upload/download."
"\nLeave blank to download all available files.")
order_type = fields.Selection(
selection=lambda self: self._selection_order_type(),
string='Order Type',
help="For most banks is France you should use the "
"format neutral Order Types 'FUL' for upload "
"and 'FDL' for download.")
test_mode = fields.Boolean(
string='Test Mode',
help="Select this option to test if the syntax of "
"the upload file is correct."
"\nThis option is only available for "
"Order Type 'FUL'.")
note = fields.Text(string='EBICS file transfer Log', readonly=True)
@api.model
def _default_ebics_config_id(self):
cfg_mod = self.env['ebics.config']
cfg = cfg_mod.search(
[('company_id', '=', self.env.user.company_id.id),
('state', '=', 'active')])
if cfg and len(cfg) == 1:
return cfg
else:
return cfg_mod
@api.model
def _selection_order_type(self):
return self.env['ebics.file.format']._selection_order_type()
@api.onchange('ebics_config_id')
def _onchange_ebics_config_id(self):
domain = {}
if self._context.get('ebics_download'):
download_formats = self.ebics_config_id.ebics_file_format_ids\
.filtered(lambda r: r.type == 'down')
if len(download_formats) == 1:
self.format_id = download_formats
domain['format_id'] = [('type', '=', 'down'),
('id', 'in', download_formats.ids)]
else:
upload_formats = self.ebics_config_id.ebics_file_format_ids\
.filtered(lambda r: r.type == 'up')
if len(upload_formats) == 1:
self.format_id = upload_formats
domain['format_id'] = [('type', '=', 'up'),
('id', 'in', upload_formats.ids)]
return {'domain': domain}
@api.onchange('upload_data')
def _onchange_upload_data(self):
self.upload_fname_dummy = self.upload_fname
self._detect_upload_format()
upload_formats = self.format_id \
or self.ebics_config_id.ebics_file_format_ids.filtered(
lambda r: r.type == 'up')
if len(upload_formats) == 1:
self.format_id = upload_formats
@api.onchange('format_id')
def _onchange_format_id(self):
self.order_type = self.format_id.order_type
@api.multi
def ebics_upload(self):
self.ensure_one()
ctx = self._context.copy()
ebics_file = self._ebics_upload()
if ebics_file:
ctx['ebics_file_id'] = ebics_file.id
module = __name__.split('addons.')[1].split('.')[0]
result_view = self.env.ref(
'%s.ebics_xfer_view_form_result' % module)
return {
'name': _('EBICS file transfer result'),
'res_id': self.id,
'view_type': 'form',
'view_mode': 'form',
'res_model': 'ebics.xfer',
'view_id': result_view.id,
'target': 'new',
'context': ctx,
'type': 'ir.actions.act_window',
}
@api.multi
def ebics_download(self):
self.ensure_one()
self.ebics_config_id._check_ebics_files()
ctx = self._context.copy()
self.note = ''
client = self._setup_client()
if client:
download_formats = self.format_id \
or self.ebics_config_id.ebics_file_format_ids.filtered(
lambda r: r.type == 'down')
ebics_files = self.env['ebics.file']
for df in download_formats:
success = False
order_type = df.order_type or 'FDL'
params = {}
if order_type == 'FDL':
params['filetype'] = df.name
if order_type in ['FDL', 'C53']:
params.update({
'start': self.date_from or None,
'end': self.date_to or None,
})
kwargs = {k: v for k, v in params.items() if v}
try:
method = getattr(client, order_type)
data = method(**kwargs)
ebics_files += self._handle_download_data(data, df)
success = True
except EbicsFunctionalError:
e = exc_info()
self.note += '\n'
self.note += _("EBICS Functional Error:")
self.note += '\n'
self.note += '%s (code: %s)' % (e[1].message, e[1].code)
except EbicsTechnicalError:
e = exc_info()
self.note += '\n'
self.note += _("EBICS Technical Error:")
self.note += '\n'
self.note += '%s (code: %s)' % (e[1].message, e[1].code)
except EbicsVerificationError:
self.note += '\n'
self.note += _("EBICS Verification Error:")
self.note += '\n'
self.note += _("The EBICS response could not be verified.")
except UserError, e:
self.note += '\n'
self.note += _("Warning:")
self.note += '\n'
self.note += e.message
except:
self.note += '\n'
self.note += _("Unknown Error")
tb = ''.join(format_exception(*exc_info()))
self.note += '\n%s' % tb
else:
# mark received data so that it is not included in further
# downloads
trans_id = client.last_trans_id
client.confirm_download(trans_id=trans_id, success=success)
ctx['ebics_file_ids'] = ebics_files._ids
if ebics_files:
self.note += '\n'
for f in ebics_files:
self.note += _(
"EBICS File '%s' is available for further processing."
) % f.name
self.note += '\n'
module = __name__.split('addons.')[1].split('.')[0]
result_view = self.env.ref(
'%s.ebics_xfer_view_form_result' % module)
return {
'name': _('EBICS file transfer result'),
'res_id': self.id,
'view_type': 'form',
'view_mode': 'form',
'res_model': 'ebics.xfer',
'view_id': result_view.id,
'target': 'new',
'context': ctx,
'type': 'ir.actions.act_window',
}
@api.multi
def button_close(self):
self.ensure_one()
return {'type': 'ir.actions.act_window_close'}
@api.multi
def view_ebics_file(self):
self.ensure_one()
module = __name__.split('addons.')[1].split('.')[0]
act = self.env['ir.actions.act_window'].for_xml_id(
module, 'ebics_file_action_download')
act['domain'] = [('id', 'in', self._context['ebics_file_ids'])]
return act
def _ebics_upload(self):
self.ensure_one()
ebics_file = self.env['ebics.file']
self.note = ''
client = self._setup_client()
if client:
upload_data = base64.decodestring(self.upload_data)
ef_format = self.format_id
OrderID = False
try:
order_type = ef_format.order_type or 'FUL'
method = hasattr(client, order_type) \
and getattr(client, order_type)
if order_type == 'FUL':
kwargs = {}
bank = self.ebics_config_id.bank_id.bank
# bank = self.ebics_config_id.bank_id.bank_id v10.0
if bank.country:
kwargs['country'] = bank.country.code
if self.test_mode:
kwargs['TEST'] = 'TRUE'
OrderID = method(ef_format.name, upload_data, **kwargs)
elif order_type in ['CCT', 'CDD', 'CDB']:
OrderID = method(upload_data)
elif order_type in ['XE2', 'XE3']:
OrderID = client.upload(order_type, upload_data)
else:
# TODO: investigate if it makes sense to support
# a generic upload for a non-predefined order_type
pass
if OrderID:
self.note += '\n'
self.note += _(
"EBICS File has been uploaded (OrderID %s)."
) % OrderID
ef_note = _("EBICS OrderID: %s") % OrderID
if self._context.get('origin'):
ef_note += '\n' + _(
"Origin: %s") % self._context['origin']
suffix = self.format_id.suffix
fn = self.upload_fname
if not fn.endswith(suffix):
fn = '.'.join[fn, suffix]
ef_vals = {
'name': self.upload_fname,
'data': self.upload_data,
'date': fields.Datetime.now(),
'format_id': self.format_id.id,
'state': 'done',
'user_id': self._uid,
'note': ef_note,
}
self._update_ef_vals(ef_vals)
ebics_file = self.env['ebics.file'].create(ef_vals)
except EbicsFunctionalError:
e = exc_info()
self.note += '\n'
self.note += _("EBICS Functional Error:")
self.note += '\n'
self.note += '%s (code: %s)' % (e[1].message, e[1].code)
except EbicsTechnicalError:
e = exc_info()
self.note += '\n'
self.note += _("EBICS Technical Error:")
self.note += '\n'
self.note += '%s (code: %s)' % (e[1].message, e[1].code)
except EbicsVerificationError:
self.note += '\n'
self.note += _("EBICS Verification Error:")
self.note += '\n'
self.note += _("The EBICS response could not be verified.")
except:
self.note += '\n'
self.note += _("Unknown Error")
tb = ''.join(format_exception(*exc_info()))
self.note += '\n%s' % tb
if self.ebics_config_id.ebics_version == 'H003' and OrderID:
self.ebics_config_id._update_order_number(OrderID)
return ebics_file
@api.multi
def _setup_client(self):
self.ebics_config_id._check_ebics_keys()
passphrase = self._get_passphrase()
keyring = EbicsKeyRing(
keys=self.ebics_config_id.ebics_keys,
passphrase=passphrase)
bank = EbicsBank(
keyring=keyring,
hostid=self.ebics_config_id.ebics_host,
url=self.ebics_config_id.ebics_url)
if self.ebics_config_id.ebics_version == 'H003':
bank._order_number = self.ebics_config_id._get_order_number()
user = EbicsUser(
keyring=keyring,
partnerid=self.ebics_config_id.ebics_partner,
userid=self.ebics_config_id.ebics_user)
signature_class = self.format_id.signature_class \
or self.ebics_config_id.signature_class
if signature_class == 'T':
user.manual_approval = True
try:
client = EbicsClient(
bank, user, version=self.ebics_config_id.ebics_version)
except:
self.note += '\n'
self.note += _("Unknown Error")
tb = ''.join(format_exception(*exc_info()))
self.note += '\n%s' % tb
client = False
return client
def _get_passphrase(self):
passphrase = self.ebics_config_id.ebics_passphrase
if passphrase:
return passphrase
module = __name__.split('addons.')[1].split('.')[0]
passphrase_view = self.env.ref(
'%s.ebics_xfer_view_form_passphrase' % module)
return {
'name': _('EBICS file transfer'),
'res_id': self.id,
'view_type': 'form',
'view_mode': 'form',
'res_model': 'ebics.xfer',
'view_id': passphrase_view.id,
'target': 'new',
'context': self._context,
'type': 'ir.actions.act_window',
}
def _file_format_methods(self):
"""
Extend this dictionary in order to add support
for extra file formats.
"""
res = {
'camt.xxx.cfonb120.stm': self._handle_cfonb120,
'camt.053.001.02.stm': self._handle_camt053,
}
return res
def _update_ef_vals(self, ef_vals):
"""
Adapt this method to customize the EBICS File values.
"""
if self.format_id and self.format_id.type == 'up':
fn = ef_vals['name']
dups = self._check_duplicate_ebics_file(
fn, self.format_id)
if dups:
n = 1
fn = '_'.join([fn, str(n)])
while self._check_duplicate_ebics_file(fn, self.format_id):
n += 1
fn = '_'.join([fn, str(n)])
ef_vals['name'] = fn
def _handle_download_data(self, data, file_format):
ebics_files = self.env['ebics.file']
if isinstance(data, dict):
for doc in data:
ebics_files += self._create_ebics_file(
data[doc], file_format, docname=doc)
else:
ebics_files += self._create_ebics_file(data, file_format)
return ebics_files
def _create_ebics_file(self, data, file_format, docname=None):
"""
Write the data as received over the EBICS connection
to a temporary file so that is is available for
analysis (e.g. in case formats are received that cannot
be handled in the current version of this module).
TODO: add code to clean-up /tmp on a regular basis.
After saving the data received we call the method to perform
file format specific processing.
"""
ebics_files_root = self.ebics_config_id.ebics_files
tmp_dir = os.path.normpath(ebics_files_root + '/tmp')
if not os.path.isdir(tmp_dir):
os.makedirs(tmp_dir, 0700)
fn_parts = [self.ebics_config_id.ebics_host]
if docname:
fn_parts.append(docname)
else:
fn_date = self.date_to or fields.Date.today()
fn_parts.append(fn_date)
base_fn = '_'.join(fn_parts)
n = 1
full_tmp_fn = os.path.normpath(tmp_dir + '/' + base_fn)
while os.path.exists(full_tmp_fn):
n += 1
tmp_fn = base_fn + '_' + str(n).rjust(3, '0')
full_tmp_fn = os.path.normpath(tmp_dir + '/' + tmp_fn)
with open(full_tmp_fn, 'wb') as f:
f.write(data)
ff_methods = self._file_format_methods()
if file_format.name in ff_methods:
data = ff_methods[file_format.name](data)
fn = '.'.join([base_fn, file_format.suffix])
dups = self._check_duplicate_ebics_file(fn, file_format)
if dups:
raise UserError(_(
"EBICS File with name '%s' has already been downloaded."
"\nPlease check this file and rename in case there is "
"no risk on duplicate transactions.")
% fn)
data = base64.encodestring(data)
ef_vals = {
'name': fn,
'data': data,
'date': fields.Datetime.now(),
'date_from': self.date_from,
'date_to': self.date_from,
'format_id': file_format.id,
'user_id': self._uid,
}
self._update_ef_vals(ef_vals)
ebics_file = self.env['ebics.file'].create(ef_vals)
return ebics_file
def _check_duplicate_ebics_file(self, fn, file_format):
dups = self.env['ebics.file'].search(
[('name', '=', fn),
('format_id', '=', file_format.id),
'|',
('company_id', '=', self.env.user.company_id.id),
('company_id', '=', False)])
return dups
def _detect_upload_format(self):
"""
Use this method in order to automatically detect and set the
EBICS upload file format.
"""
pass
def _update_order_number(self, OrderID):
o_list = list(OrderID)
for i, c in enumerate(reversed(o_list), start=1):
if c == '9':
o_list[-i] = 'A'
break
if c == 'Z':
continue
else:
o_list[-i] = chr(ord(c) + 1)
break
next = ''.join(o_list)
if next == 'ZZZZ':
next = 'A000'
self.ebics_config_id.order_number = next
def _insert_line_terminator(self, data_in, line_len):
data_out = ''
max = len(data_in)
i = 0
while i + line_len <= max:
data_out += data_in[i:i + line_len] + '\n'
i += line_len
return data_out
def _handle_cfonb120(self, data_in):
return self._insert_line_terminator(data_in, 120)
def _handle_cfonb240(self, data_in):
return self._insert_line_terminator(data_in, 240)
def _handle_camt053(self, data_in):
"""
Use this method if you need to fix camt files received
from your bank before passing them to the
Odoo Enterprise or Community CAMT parser.
"""
return data_in

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="ebics_xfer_view_form_download" model="ir.ui.view">
<field name="name">EBICS File Download</field>
<field name="model">ebics.xfer</field>
<field name="priority">1</field>
<field name="arch" type="xml">
<form string="EBICS File Download">
<group>
<separator string="Select your bank :" colspan="2"/>
<field name="ebics_config_id" options="{'no_create': True, 'no_open': True}"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="format_id"/>
</group>
<footer>
<button name="ebics_download" string="Download Files" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="ebics_xfer_view_form_upload" model="ir.ui.view">
<field name="name">EBICS File Upload</field>
<field name="model">ebics.xfer</field>
<field name="priority">1</field>
<field name="arch" type="xml">
<form string="EBICS File Upload">
<group>
<separator string="Select your bank :" colspan="2"/>
<field name="ebics_config_id" options="{'no_create': True, 'no_open': True}"/>
<separator string="Select your file :" colspan="2"/>
<field name="upload_data" filename="upload_fname" required="1"/>
<field name="upload_fname" invisible="1"/>
<field name="upload_fname_dummy"/>
<field name="format_id" required="1"/>
<field name="order_type"/>
<field name="test_mode" attrs="{'invisible': [('order_type', '!=', 'FUL')]}"/>
</group>
<footer>
<button name="ebics_upload" string="Upload File" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="ebics_xfer_view_form_result" model="ir.ui.view">
<field name="name">EBICS File Transfer</field>
<field name="model">ebics.xfer</field>
<field name="priority">2</field>
<field name="arch" type="xml">
<form string="EBICS File Transfer">
<separator colspan="4" string="Results :" />
<field name="note" colspan="4" nolabel="1" width="850" height="400"/>
<footer>
<button name="view_ebics_file" type="object" string="View EBICS File(s)" class="oe_highlight"
invisible="not context.get('ebics_file_ids')"/>
<button name="button_close" type="object" string="Close"/>
</footer>
</form>
</field>
</record>
<record id="ebics_xfer_action_download" model="ir.actions.act_window">
<field name="name">EBICS File Transfer</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">ebics.xfer</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">{'ebics_download': 1}</field>
<field name="view_id" ref="ebics_xfer_view_form_download"/>
</record>
<record id="ebics_xfer_action_upload" model="ir.actions.act_window">
<field name="name">EBICS File Transfer</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">ebics.xfer</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">{'ebics_upload': 1}</field>
<field name="view_id" ref="ebics_xfer_view_form_upload"/>
</record>
<menuitem id="ebics_xfer_menu_download" name="EBICS Download" parent="ebics_processing_menu" action="ebics_xfer_action_download" sequence="10"/>
<menuitem id="ebics_xfer_menu_upload" name="EBICS Upload" parent="ebics_processing_menu" action="ebics_xfer_action_upload" sequence="20"/>
</data>
</openerp>

View File

@ -0,0 +1,24 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3
==============================
Upload Payment Order via EBICS
==============================
This module allows to upload a CCT Payment Order to the bank via the EBICS protocol.
Installation
============
This module depends upon the following modules (cf. apps.odoo.com):
- account_ebics
- account_banking_sepa_credit_transfer
Usage
=====
Create your Payment Order and generate the ISO 20022 Credit Transfer File.
The wizard that allows to download this XML file now has an extra button called 'EBICS Upload'
in order to send this file directly to your bank.

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models
from . import wizard

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2018 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Upload Payment Order via EBICS',
'version': '8.0.1.1.0',
'license': 'AGPL-3',
'author': 'Noviat',
'category': 'Accounting & Finance',
'depends': [
'account_ebics',
'account_banking_sepa_credit_transfer'],
'data': [
'views/payment_order.xml',
'wizard/banking_export_sepa_wizard.xml',
],
'installable': True,
}

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import payment_order

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2018 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import fields, models
class PaymentOrder(models.Model):
_inherit = 'payment.order'
date_ebics_upload = fields.Date(
string='EBICS upload date',
readonly=True)

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_payment_order_form" model="ir.ui.view">
<field name="name">payment.order.form.ebics</field>
<field name="inherit_id" ref="account_payment.view_payment_order_form" />
<field name="model">payment.order</field>
<field name="arch" type="xml">
<field name="date_prefered" position="after">
<field name="date_ebics_upload"/>
</field>
</field>
</record>
<record id="view_payment_order_tree" model="ir.ui.view">
<field name="name">payment.order.tree.ebics</field>
<field name="inherit_id" ref="account_payment.view_payment_order_tree"/>
<field name="model">payment.order</field>
<field name="arch" type="xml">
<field name="date_created" position="after">
<field name="date_ebics_upload"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import banking_export_sepa_wizard
from . import ebics_xfer

View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2017 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import api, models, _
class BankingExportSepaWizard(models.TransientModel):
_inherit = 'banking.export.sepa.wizard'
@api.multi
def ebics_upload(self):
self.ensure_one()
ctx = self._context.copy()
payment_order = self.payment_order_ids[0]
origin = _("Payment Order") + ': ' + payment_order.reference
ctx.update({
'default_upload_data': self.file,
'default_upload_fname': self.filename,
'origin': origin,
'payment_order_id': payment_order.id,
})
ebics_xfer = self.env['ebics.xfer'].with_context(ctx).create({})
ebics_xfer._onchange_upload_data()
ebics_xfer._onchange_format_id()
view = self.env.ref('account_ebics.ebics_xfer_view_form_upload')
act = {
'name': _('EBICS Upload'),
'view_type': 'form',
'view_mode': 'form',
'res_model': 'ebics.xfer',
'view_id': view.id,
'res_id': ebics_xfer.id,
'type': 'ir.actions.act_window',
'target': 'new',
'context': ctx,
}
return act

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="banking_export_sepa_wizard_view" model="ir.ui.view">
<field name="name">banking.export.sepa.wizard.view</field>
<field name="model">banking.export.sepa.wizard</field>
<field name="inherit_id" ref="account_banking_sepa_credit_transfer.banking_export_sepa_wizard_view"/>
<field name="arch" type="xml">
<button name="save_sepa" position="before">
<button name="ebics_upload" string="EBICS Upload" type="object" class="oe_highlight" states="finish"/>
</button>
</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright 2009-2018 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import models
class EbicsXfer(models.TransientModel):
_inherit = 'ebics.xfer'
def _ebics_upload(self):
payment_order_id = self._context.get('payment_order_id')
ebics_file = super(EbicsXfer, self)._ebics_upload()
if ebics_file and payment_order_id:
order = self.env['payment.order'].browse(payment_order_id)
order.date_ebics_upload = ebics_file.date
return ebics_file