mirror of
https://github.com/brain-tec/account_ebics.git
synced 2024-11-27 14:37:25 +00:00
add ebics module
This commit is contained in:
parent
539bd17530
commit
910b8bec0a
69
account_ebics/README.rst
Normal file
69
account_ebics/README.rst
Normal 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).
|
2
account_ebics/__init__.py
Normal file
2
account_ebics/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from . import models
|
||||||
|
from . import wizard
|
24
account_ebics/__manifest__.py
Normal file
24
account_ebics/__manifest__.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Copyright 2009-2018 Noviat.
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'EBICS banking protocol',
|
||||||
|
'version': '11.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,
|
||||||
|
}
|
94
account_ebics/data/ebics_file_format.xml
Normal file
94
account_ebics/data/ebics_file_format.xml
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<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>
|
||||||
|
</odoo>
|
BIN
account_ebics/doc/EBICS_Annex1_ReturnCodes_final-16-05-2011.pdf
Normal file
BIN
account_ebics/doc/EBICS_Annex1_ReturnCodes_final-16-05-2011.pdf
Normal file
Binary file not shown.
Binary file not shown.
BIN
account_ebics/doc/EBICS_Common_IG_based_EBICS_2.5.pdf
Normal file
BIN
account_ebics/doc/EBICS_Common_IG_based_EBICS_2.5.pdf
Normal file
Binary file not shown.
BIN
account_ebics/doc/EBICS_Specification_2.5_final-16-05-2011.pdf
Normal file
BIN
account_ebics/doc/EBICS_Specification_2.5_final-16-05-2011.pdf
Normal file
Binary file not shown.
5
account_ebics/models/__init__.py
Normal file
5
account_ebics/models/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from . import fintech_ebics_register
|
||||||
|
from . import account_bank_statement
|
||||||
|
from . import ebics_config
|
||||||
|
from . import ebics_file
|
||||||
|
from . import ebics_file_format
|
11
account_ebics/models/account_bank_statement.py
Normal file
11
account_ebics/models/account_bank_statement.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Copyright 2009-2018 Noviat.
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class AccountBankStatement(models.Model):
|
||||||
|
_inherit = 'account.bank.statement'
|
||||||
|
|
||||||
|
ebics_file_id = fields.Many2one(
|
||||||
|
comodel_name='ebics.file', string='EBICS Data File')
|
494
account_ebics/models/ebics_config.py
Normal file
494
account_ebics/models/ebics_config.py
Normal file
@ -0,0 +1,494 @@
|
|||||||
|
# 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 urllib.error import URLError
|
||||||
|
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import 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:
|
||||||
|
exctype, value = exc_info()[:2]
|
||||||
|
raise UserError(_(
|
||||||
|
"urlopen error:\n url '%s' - %s")
|
||||||
|
% (self.ebics_url, str(value)))
|
||||||
|
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.bank_id.country.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, mode=0o700)
|
||||||
|
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_id.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()
|
||||||
|
public_bank_keys = public_bank_keys.encode()
|
||||||
|
tmp_dir = os.path.normpath(self.ebics_files + '/tmp')
|
||||||
|
if not os.path.isdir(tmp_dir):
|
||||||
|
os.makedirs(tmp_dir, mode=0o700)
|
||||||
|
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)
|
262
account_ebics/models/ebics_file.py
Normal file
262
account_ebics/models/ebics_file.py
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
# Copyright 2009-2018 Noviat.
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import 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,
|
||||||
|
}
|
||||||
|
wiz = self.env[wiz_model].create(wiz_vals)
|
||||||
|
res = wiz.import_file()
|
||||||
|
if res.get('res_model') \
|
||||||
|
== 'account.bank.statement.import.journal.creation':
|
||||||
|
if res.get('context'):
|
||||||
|
bank_account = res['context'].get('default_bank_acc_number')
|
||||||
|
raise UserError(_(
|
||||||
|
"No financial journal found for Company Bank Account %s"
|
||||||
|
) % bank_account)
|
||||||
|
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)
|
79
account_ebics/models/ebics_file_format.py
Normal file
79
account_ebics/models/ebics_file_format.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Copyright 2009-2018 Noviat.
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo 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
|
40
account_ebics/models/fintech_ebics_register.py
Normal file
40
account_ebics/models/fintech_ebics_register.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# 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 odoo.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 as 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()
|
27
account_ebics/security/ebics_security.xml
Normal file
27
account_ebics/security/ebics_security.xml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<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 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>
|
||||||
|
|
||||||
|
</odoo>
|
7
account_ebics/security/ir.model.access.csv
Normal file
7
account_ebics/security/ir.model.access.csv
Normal 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
|
|
BIN
account_ebics/static/description/icon.png
Normal file
BIN
account_ebics/static/description/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
122
account_ebics/views/ebics_config.xml
Normal file
122
account_ebics/views/ebics_config.xml
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<label string=""/>
|
||||||
|
<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"/>
|
||||||
|
|
||||||
|
</odoo>
|
216
account_ebics/views/ebics_file.xml
Normal file
216
account_ebics/views/ebics_file.xml
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<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"/>
|
||||||
|
|
||||||
|
</odoo>
|
50
account_ebics/views/ebics_file_format.xml
Normal file
50
account_ebics/views/ebics_file_format.xml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<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"/>
|
||||||
|
|
||||||
|
</odoo>
|
15
account_ebics/views/menuitem.xml
Normal file
15
account_ebics/views/menuitem.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<menuitem id="ebics_processing_menu"
|
||||||
|
name="EBICS Processing"
|
||||||
|
parent="account.menu_finance"
|
||||||
|
sequence="4"/>
|
||||||
|
|
||||||
|
<menuitem id="ebics_menu"
|
||||||
|
name="EBICS"
|
||||||
|
parent='account.menu_finance_configuration'
|
||||||
|
groups="account_ebics.group_ebics_manager"
|
||||||
|
sequence="100"/>
|
||||||
|
|
||||||
|
</odoo>
|
2
account_ebics/wizard/__init__.py
Normal file
2
account_ebics/wizard/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from . import ebics_change_passphrase
|
||||||
|
from . import ebics_xfer
|
77
account_ebics/wizard/ebics_change_passphrase.py
Normal file
77
account_ebics/wizard/ebics_change_passphrase.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# Copyright 2009-2018 Noviat.
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import 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 as 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'}
|
39
account_ebics/wizard/ebics_change_passphrase.xml
Normal file
39
account_ebics/wizard/ebics_change_passphrase.xml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
</odoo>
|
541
account_ebics/wizard/ebics_xfer.py
Normal file
541
account_ebics/wizard/ebics_xfer.py
Normal file
@ -0,0 +1,541 @@
|
|||||||
|
# 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 odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import 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 as 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 v8.0
|
||||||
|
bank = self.ebics_config_id.bank_id.bank_id
|
||||||
|
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, mode=0o700)
|
||||||
|
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 = b''
|
||||||
|
max = len(data_in)
|
||||||
|
i = 0
|
||||||
|
while i + line_len <= max:
|
||||||
|
data_out += data_in[i:i + line_len] + b'\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
|
94
account_ebics/wizard/ebics_xfer.xml
Normal file
94
account_ebics/wizard/ebics_xfer.xml
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<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"/>
|
||||||
|
|
||||||
|
</odoo>
|
Loading…
Reference in New Issue
Block a user