[14.0]black, isort, prettier

This commit is contained in:
Luc De Meyer 2022-05-10 21:40:54 +02:00
parent 79250ab61a
commit ffac2e999f
30 changed files with 1463 additions and 1106 deletions

View File

@ -25,6 +25,7 @@ Remark:
The EBICS 'Test Mode' for uploading orders requires Fintech 4.3.4 or higher. The EBICS 'Test Mode' for uploading orders requires Fintech 4.3.4 or higher.
SWIFT 3SKey support requires Fintech 6.4 or higher. SWIFT 3SKey support requires Fintech 6.4 or higher.
| |
We also recommend to consider the installation of the following modules: We also recommend to consider the installation of the following modules:

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<data noupdate="1"> <data noupdate="1">
@ -9,7 +9,9 @@
<field name="type">down</field> <field name="type">down</field>
<field name="order_type">C52</field> <field name="order_type">C52</field>
<field name="download_process_method">camt.052</field> <field name="download_process_method">camt.052</field>
<field name="description">bank to customer account report in format camt.052</field> <field
name="description"
>bank to customer account report in format camt.052</field>
<field name="suffix">c52.xml</field> <field name="suffix">c52.xml</field>
</record> </record>
@ -18,16 +20,20 @@
<field name="type">down</field> <field name="type">down</field>
<field name="order_type">Z52</field> <field name="order_type">Z52</field>
<field name="download_process_method">camt.052</field> <field name="download_process_method">camt.052</field>
<field name="description">bank to customer account report in format camt.052</field> <field
name="description"
>bank to customer account report in format camt.052</field>
<field name="suffix">c52.xml</field> <field name="suffix">c52.xml</field>
</record> </record>
<record id="ebics_ff_C53" model="ebics.file.format"> <record id="ebics_ff_C53" model="ebics.file.format">
<field name="name">camt.053</field> <field name="name">camt.053</field>
<field name="type">down</field> <field name="type">down</field>
<field name="order_type">C53</field> <field name="order_type">C53</field>
<field name="download_process_method">camt.053</field> <field name="download_process_method">camt.053</field>
<field name="description">Bank to customer statement report in format camt.053</field> <field
name="description"
>Bank to customer statement report in format camt.053</field>
<field name="suffix">c53.xml</field> <field name="suffix">c53.xml</field>
</record> </record>
@ -36,16 +42,20 @@
<field name="type">down</field> <field name="type">down</field>
<field name="order_type">Z53</field> <field name="order_type">Z53</field>
<field name="download_process_method">camt.053</field> <field name="download_process_method">camt.053</field>
<field name="description">Bank to customer statement report in format camt.053</field> <field
name="description"
>Bank to customer statement report in format camt.053</field>
<field name="suffix">c53.xml</field> <field name="suffix">c53.xml</field>
</record> </record>
<record id="ebics_ff_C54" model="ebics.file.format"> <record id="ebics_ff_C54" model="ebics.file.format">
<field name="name">camt.054</field> <field name="name">camt.054</field>
<field name="type">down</field> <field name="type">down</field>
<field name="order_type">C54</field> <field name="order_type">C54</field>
<field name="download_process_method">camt.054</field> <field name="download_process_method">camt.054</field>
<field name="description">Bank to customer debit credit notification in format camt.054</field> <field
name="description"
>Bank to customer debit credit notification in format camt.054</field>
<field name="suffix">c52.xml</field> <field name="suffix">c52.xml</field>
</record> </record>
@ -54,7 +64,9 @@
<field name="type">down</field> <field name="type">down</field>
<field name="order_type">Z54</field> <field name="order_type">Z54</field>
<field name="download_process_method">camt.054</field> <field name="download_process_method">camt.054</field>
<field name="description">Bank to customer debit credit notification in format camt.054</field> <field
name="description"
>Bank to customer debit credit notification in format camt.054</field>
<field name="suffix">c52.xml</field> <field name="suffix">c52.xml</field>
</record> </record>
@ -63,7 +75,9 @@
<field name="type">down</field> <field name="type">down</field>
<field name="order_type">FDL</field> <field name="order_type">FDL</field>
<field name="download_process_method">cfonb120</field> <field name="download_process_method">cfonb120</field>
<field name="description">Bank to customer statement report in format cfonb120</field> <field
name="description"
>Bank to customer statement report in format cfonb120</field>
<field name="suffix">cfonb120.dat</field> <field name="suffix">cfonb120.dat</field>
</record> </record>
@ -71,19 +85,23 @@
<field name="name">pain.002</field> <field name="name">pain.002</field>
<field name="type">down</field> <field name="type">down</field>
<field name="order_type">CDZ</field> <field name="order_type">CDZ</field>
<field name="description">Payment status report for direct debit in format pain.002</field> <field
name="description"
>Payment status report for direct debit in format pain.002</field>
<field name="suffix">psr.xml</field> <field name="suffix">psr.xml</field>
</record> </record>
<record id="ebics_ff_Z01" model="ebics.file.format"> <record id="ebics_ff_Z01" model="ebics.file.format">
<field name="name">pain.002</field> <field name="name">pain.002</field>
<field name="type">down</field> <field name="type">down</field>
<field name="order_type">Z01</field> <field name="order_type">Z01</field>
<field name="download_process_method">pain.002</field> <field name="download_process_method">pain.002</field>
<field name="description">Payment status report for direct debit in format pain.002</field> <field
name="description"
>Payment status report for direct debit in format pain.002</field>
<field name="suffix">psr.xml</field> <field name="suffix">psr.xml</field>
</record> </record>
<!-- Upload formats --> <!-- Upload formats -->
<record id="ebics_ff_LCR" model="ebics.file.format"> <record id="ebics_ff_LCR" model="ebics.file.format">
@ -114,7 +132,9 @@
<field name="name">pain.008.001.02.sdd</field> <field name="name">pain.008.001.02.sdd</field>
<field name="type">up</field> <field name="type">up</field>
<field name="order_type">CDD</field> <field name="order_type">CDD</field>
<field name="description">Sepa Core Direct Debit Order in format pain.008.001.02</field> <field
name="description"
>Sepa Core Direct Debit Order in format pain.008.001.02</field>
<field name="suffix">xml</field> <field name="suffix">xml</field>
</record> </record>
@ -122,15 +142,19 @@
<field name="name">pain.008.001.02.sdd</field> <field name="name">pain.008.001.02.sdd</field>
<field name="type">up</field> <field name="type">up</field>
<field name="order_type">XE3</field> <field name="order_type">XE3</field>
<field name="description">Sepa Core Direct Debit Order in format pain.008.001.02</field> <field
name="description"
>Sepa Core Direct Debit Order in format pain.008.001.02</field>
<field name="suffix">xml</field> <field name="suffix">xml</field>
</record> </record>
<record id="ebics_ff_CDB" model="ebics.file.format"> <record id="ebics_ff_CDB" model="ebics.file.format">
<field name="name">pain.008.001.02.sbb</field> <field name="name">pain.008.001.02.sbb</field>
<field name="type">up</field> <field name="type">up</field>
<field name="order_type">CDB</field> <field name="order_type">CDB</field>
<field name="description">Sepa Direct Debit (B2B) Order in format pain.008.001.02</field> <field
name="description"
>Sepa Direct Debit (B2B) Order in format pain.008.001.02</field>
<field name="suffix">xml</field> <field name="suffix">xml</field>
</record> </record>
@ -138,7 +162,9 @@
<field name="name">pain.008.001.02.sbb</field> <field name="name">pain.008.001.02.sbb</field>
<field name="type">up</field> <field name="type">up</field>
<field name="order_type">XE4</field> <field name="order_type">XE4</field>
<field name="description">Sepa Direct Debit (B2B) Order in format pain.008.001.02</field> <field
name="description"
>Sepa Direct Debit (B2B) Order in format pain.008.001.02</field>
<field name="suffix">xml</field> <field name="suffix">xml</field>
</record> </record>

View File

@ -3,16 +3,20 @@
<record id="ebics_config_comp_rule" model="ir.rule"> <record id="ebics_config_comp_rule" model="ir.rule">
<field name="name">EBICS Configuration model company rule</field> <field name="name">EBICS Configuration model company rule</field>
<field name="model_id" ref="model_ebics_config"/> <field name="model_id" ref="model_ebics_config" />
<field eval="True" name="global"/> <field eval="True" name="global" />
<field name="domain_force">['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)]</field> <field
name="domain_force"
>['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)]</field>
</record> </record>
<record id="ebics_file_comp_rule" model="ir.rule"> <record id="ebics_file_comp_rule" model="ir.rule">
<field name="name">EBICS File model company rule</field> <field name="name">EBICS File model company rule</field>
<field name="model_id" ref="model_ebics_file"/> <field name="model_id" ref="model_ebics_file" />
<field eval="True" name="global"/> <field eval="True" name="global" />
<field name="domain_force">['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)]</field> <field
name="domain_force"
>['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)]</field>
</record> </record>
</odoo> </odoo>

View File

@ -1,9 +1,10 @@
# Copyright 2009-2020 Noviat. # Copyright 2009-2020 Noviat.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openupgradelib import openupgrade # pylint: disable=W7936
import os import os
from openupgradelib import openupgrade # pylint: disable=W7936
@openupgrade.migrate() @openupgrade.migrate()
def migrate(env, version): def migrate(env, version):
@ -16,58 +17,67 @@ def _ebics_config_upgrade(env, version):
env.cr.execute("SELECT * FROM ebics_config") env.cr.execute("SELECT * FROM ebics_config")
cfg_datas = env.cr.dictfetchall() cfg_datas = env.cr.dictfetchall()
for cfg_data in cfg_datas: for cfg_data in cfg_datas:
cfg = env['ebics.config'].browse(cfg_data['id']) cfg = env["ebics.config"].browse(cfg_data["id"])
journal = env['account.journal'].search( journal = env["account.journal"].search(
[('bank_account_id', '=', cfg_data['bank_id'])]) [("bank_account_id", "=", cfg_data["bank_id"])]
keys_fn_old = cfg_data['ebics_keys'] )
keys_fn_old = cfg_data["ebics_keys"]
ebics_keys_root = os.path.dirname(keys_fn_old) ebics_keys_root = os.path.dirname(keys_fn_old)
if os.path.isfile(keys_fn_old): if os.path.isfile(keys_fn_old):
keys_fn = ebics_keys_root + '/' + cfg_data['ebics_user'] + '_keys' keys_fn = ebics_keys_root + "/" + cfg_data["ebics_user"] + "_keys"
os.rename(keys_fn_old, keys_fn) os.rename(keys_fn_old, keys_fn)
state = cfg_data['state'] == 'active' and 'confirm' or 'draft' state = cfg_data["state"] == "active" and "confirm" or "draft"
cfg.write({ cfg.write(
'company_ids': [(6, 0, [cfg_data['company_id']])], {
'journal_ids': [(6, 0, journal.ids)], "company_ids": [(6, 0, [cfg_data["company_id"]])],
'ebics_keys': ebics_keys_root, "journal_ids": [(6, 0, journal.ids)],
'state': state, "ebics_keys": ebics_keys_root,
}) "state": state,
}
)
user_vals = { user_vals = {
'ebics_config_id': cfg_data['id'], "ebics_config_id": cfg_data["id"],
'name': cfg_data['ebics_user'], "name": cfg_data["ebics_user"],
} }
for fld in [ for fld in [
'signature_class', 'ebics_passphrase', "signature_class",
'ebics_ini_letter_fn', 'ebics_public_bank_keys_fn', "ebics_passphrase",
'ebics_key_x509', 'ebics_key_x509_dn_cn', "ebics_ini_letter_fn",
'ebics_key_x509_dn_o', 'ebics_key_x509_dn_ou', "ebics_public_bank_keys_fn",
'ebics_key_x509_dn_c', 'ebics_key_x509_dn_st', "ebics_key_x509",
'ebics_key_x509_dn_l', 'ebics_key_x509_dn_e', "ebics_key_x509_dn_cn",
'ebics_file_format_ids', 'state']: "ebics_key_x509_dn_o",
"ebics_key_x509_dn_ou",
"ebics_key_x509_dn_c",
"ebics_key_x509_dn_st",
"ebics_key_x509_dn_l",
"ebics_key_x509_dn_e",
"ebics_file_format_ids",
"state",
]:
if cfg_data.get(fld): if cfg_data.get(fld):
if fld == 'ebics_file_format_ids': if fld == "ebics_file_format_ids":
user_vals[fld] = [(6, 0, cfg_data[fld])] user_vals[fld] = [(6, 0, cfg_data[fld])]
elif fld == 'state' and cfg_data['state'] == 'active': elif fld == "state" and cfg_data["state"] == "active":
user_vals['state'] = 'active_keys' user_vals["state"] = "active_keys"
else: else:
user_vals[fld] = cfg_data[fld] user_vals[fld] = cfg_data[fld]
ebics_userid = env['ebics.userid'].create(user_vals) ebics_userid = env["ebics.userid"].create(user_vals)
env.cr.execute( env.cr.execute(
""" """
UPDATE ir_attachment UPDATE ir_attachment
SET res_model = 'ebics.userid', res_id = %s SET res_model = 'ebics.userid', res_id = %s
WHERE name in ('ebics_ini_letter', 'ebics_public_bank_keys'); WHERE name in ('ebics_ini_letter', 'ebics_public_bank_keys');
""" """
% ebics_userid.id) % ebics_userid.id
)
if len(cfg_datas) == 1: if len(cfg_datas) == 1:
env.cr.execute( env.cr.execute("UPDATE ebics_file SET ebics_userid_id = %s" % ebics_userid.id)
"UPDATE ebics_file SET ebics_userid_id = %s" % ebics_userid.id)
def _noupdate_changes(env, version): def _noupdate_changes(env, version):
openupgrade.load_data( openupgrade.load_data(
env.cr, env.cr, "account_ebics", "migrations/13.0.1.1/noupdate_changes.xml"
'account_ebics',
'migrations/13.0.1.1/noupdate_changes.xml'
) )

View File

@ -2,16 +2,18 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
_FILE_FORMATS = [ _FILE_FORMATS = [
{'xml_id_name': 'ebics_ff_C52', {
'download_process_method': 'camt.052', "xml_id_name": "ebics_ff_C52",
}, "download_process_method": "camt.052",
{'xml_id_name': 'ebics_ff_C53', },
'download_process_method': 'camt.053', {
}, "xml_id_name": "ebics_ff_C53",
{'xml_id_name': 'ebics_ff_FDL_camt_xxx_cfonb120_stm', "download_process_method": "camt.053",
'download_process_method': 'cfonb120', },
}, {
"xml_id_name": "ebics_ff_FDL_camt_xxx_cfonb120_stm",
"download_process_method": "cfonb120",
},
] ]
@ -21,21 +23,22 @@ def migrate(cr, version):
def _update_file_format(cr, ff): def _update_file_format(cr, ff):
cr.execute( cr.execute( # pylint: disable=E8103
""" """
SELECT res_id FROM ir_model_data SELECT res_id FROM ir_model_data
WHERE module='account_ebics' AND name='{}' WHERE module='account_ebics' AND name='{}'
""".format(ff['xml_id_name']) """.format(
ff["xml_id_name"]
)
) )
res = cr.fetchone() res = cr.fetchone()
if res: if res:
cr.execute( cr.execute( # pylint: disable=E8103
""" """
UPDATE ebics_file_format UPDATE ebics_file_format
SET download_process_method='{download_process_method}' SET download_process_method='{download_process_method}'
WHERE id={ff_id}; WHERE id={ff_id};
""".format( """.format(
download_process_method=ff['download_process_method'], download_process_method=ff["download_process_method"], ff_id=res[0]
ff_id=res[0] )
)
) )

View File

@ -2,36 +2,45 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
_FILE_FORMATS = [ _FILE_FORMATS = [
{'old_xml_id_name': 'ebics_ff_camt_052_001_02_stm', {
'new_xml_id_name': 'ebics_ff_C52', "old_xml_id_name": "ebics_ff_camt_052_001_02_stm",
'new_name': 'camt.052', "new_xml_id_name": "ebics_ff_C52",
}, "new_name": "camt.052",
{'old_xml_id_name': 'ebics_ff_camt_053_001_02_stm', },
'new_xml_id_name': 'ebics_ff_C53', {
'new_name': 'camt.053', "old_xml_id_name": "ebics_ff_camt_053_001_02_stm",
}, "new_xml_id_name": "ebics_ff_C53",
{'old_xml_id_name': 'ebics_ff_camt_xxx_cfonb120_stm', "new_name": "camt.053",
'new_xml_id_name': 'ebics_ff_FDL_camt_xxx_cfonb120_stm', },
}, {
{'old_xml_id_name': 'ebics_ff_pain_001_001_03_sct', "old_xml_id_name": "ebics_ff_camt_xxx_cfonb120_stm",
'new_xml_id_name': 'ebics_ff_CCT', "new_xml_id_name": "ebics_ff_FDL_camt_xxx_cfonb120_stm",
}, },
{'old_xml_id_name': 'ebics_ff_pain_001', {
'new_xml_id_name': 'ebics_ff_XE2', "old_xml_id_name": "ebics_ff_pain_001_001_03_sct",
'new_name': 'pain.001.001.03', "new_xml_id_name": "ebics_ff_CCT",
}, },
{'old_xml_id_name': 'ebics_ff_pain_008_001_02_sdd', {
'new_xml_id_name': 'ebics_ff_CDD', "old_xml_id_name": "ebics_ff_pain_001",
}, "new_xml_id_name": "ebics_ff_XE2",
{'old_xml_id_name': 'ebics_ff_pain_008', "new_name": "pain.001.001.03",
'new_xml_id_name': 'ebics_ff_XE3', },
}, {
{'old_xml_id_name': 'ebics_ff_pain_008_001_02_sbb', "old_xml_id_name": "ebics_ff_pain_008_001_02_sdd",
'new_xml_id_name': 'ebics_ff_CDB', "new_xml_id_name": "ebics_ff_CDD",
}, },
{'old_xml_id_name': 'ebics_ff_pain_001_001_02_sct', {
'new_xml_id_name': 'ebics_ff_FUL_pain_001_001_02_sct', "old_xml_id_name": "ebics_ff_pain_008",
}, "new_xml_id_name": "ebics_ff_XE3",
},
{
"old_xml_id_name": "ebics_ff_pain_008_001_02_sbb",
"new_xml_id_name": "ebics_ff_CDB",
},
{
"old_xml_id_name": "ebics_ff_pain_001_001_02_sct",
"new_xml_id_name": "ebics_ff_FUL_pain_001_001_02_sct",
},
] ]
@ -44,11 +53,13 @@ def migrate(cr, version):
def _update_file_format(cr, ff): def _update_file_format(cr, ff):
cr.execute( cr.execute( # pylint: disable=E8103
""" """
SELECT id, res_id FROM ir_model_data SELECT id, res_id FROM ir_model_data
WHERE module='account_ebics' AND name='{}' WHERE module='account_ebics' AND name='{}'
""".format(ff['old_xml_id_name']) """.format(
ff["old_xml_id_name"]
)
) )
res = cr.fetchone() res = cr.fetchone()
if res: if res:
@ -59,7 +70,7 @@ def _update_file_format(cr, ff):
""".format( """.format(
new_xml_id_name=ff["new_xml_id_name"], xml_id=res[0] new_xml_id_name=ff["new_xml_id_name"], xml_id=res[0]
) )
if ff.get('new_name'): if ff.get("new_name"):
query += """ query += """
UPDATE ebics_file_format UPDATE ebics_file_format
SET name='{new_name}' SET name='{new_name}'
@ -67,4 +78,4 @@ def _update_file_format(cr, ff):
""".format( """.format(
new_name=ff["new_name"], ff_id=res[1] new_name=ff["new_name"], ff_id=res[1]
) )
cr.execute(query) cr.execute(query) # pylint: disable=E8103

View File

@ -5,7 +5,6 @@ from odoo import fields, models
class AccountBankStatement(models.Model): class AccountBankStatement(models.Model):
_inherit = 'account.bank.statement' _inherit = "account.bank.statement"
ebics_file_id = fields.Many2one( ebics_file_id = fields.Many2one(comodel_name="ebics.file", string="EBICS Data File")
comodel_name='ebics.file', string='EBICS Data File')

View File

@ -1,9 +1,9 @@
# Copyright 2009-2020 Noviat. # Copyright 2009-2022 Noviat.
# License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl).
import logging import logging
import re
import os import os
import re
from odoo import _, api, fields, models from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
@ -16,118 +16,151 @@ class EbicsConfig(models.Model):
EBICS configuration is stored in a separate object in order to EBICS configuration is stored in a separate object in order to
allow extra security policies on this object. allow extra security policies on this object.
""" """
_name = 'ebics.config'
_description = 'EBICS Configuration' _name = "ebics.config"
_order = 'name' _description = "EBICS Configuration"
_order = "name"
name = fields.Char( name = fields.Char(
string='Name', string="Name",
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
required=True) states={"draft": [("readonly", False)]},
required=True,
)
journal_ids = fields.Many2many( journal_ids = fields.Many2many(
comodel_name='account.journal', comodel_name="account.journal",
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
string='Bank Accounts', states={"draft": [("readonly", False)]},
string="Bank Accounts",
domain="[('type', '=', 'bank')]", domain="[('type', '=', 'bank')]",
required=True) required=True,
)
ebics_host = fields.Char( ebics_host = fields.Char(
string='EBICS HostID', required=True, string="EBICS HostID",
readonly=True, states={'draft': [('readonly', False)]}, required=True,
readonly=True,
states={"draft": [("readonly", False)]},
help="Contact your bank to get the EBICS HostID." help="Contact your bank to get the EBICS HostID."
"\nIn France the BIC is usually allocated to the HostID " "\nIn France the BIC is usually allocated to the HostID "
"whereas in Germany it tends to be an institute specific string " "whereas in Germany it tends to be an institute specific string "
"of 8 characters.") "of 8 characters.",
)
ebics_url = fields.Char( ebics_url = fields.Char(
string='EBICS URL', required=True, string="EBICS URL",
readonly=True, states={'draft': [('readonly', False)]}, required=True,
help="Contact your bank to get the EBICS URL.") readonly=True,
states={"draft": [("readonly", False)]},
help="Contact your bank to get the EBICS URL.",
)
ebics_version = fields.Selection( ebics_version = fields.Selection(
selection=[('H003', 'H003 (2.4)'), selection=[("H003", "H003 (2.4)"), ("H004", "H004 (2.5)")],
('H004', 'H004 (2.5)')], string="EBICS protocol version",
string='EBICS protocol version', readonly=True,
readonly=True, states={'draft': [('readonly', False)]}, states={"draft": [("readonly", False)]},
required=True, default='H004') required=True,
default="H004",
)
ebics_partner = fields.Char( ebics_partner = fields.Char(
string='EBICS PartnerID', required=True, string="EBICS PartnerID",
readonly=True, states={'draft': [('readonly', False)]}, required=True,
readonly=True,
states={"draft": [("readonly", False)]},
help="Organizational unit (company or individual) " help="Organizational unit (company or individual) "
"that concludes a contract with the bank. " "that concludes a contract with the bank. "
"\nIn this contract it will be agreed which order types " "\nIn this contract it will be agreed which order types "
"(file formats) are used, which accounts are concerned, " "(file formats) are used, which accounts are concerned, "
"which of the customer's users (subscribers) " "which of the customer's users (subscribers) "
"communicate with the EBICS bank server and the authorisations " "communicate with the EBICS bank server and the authorisations "
"that these users will possess. " "that these users will possess. "
"\nIt is identified by the PartnerID.") "\nIt is identified by the PartnerID.",
)
ebics_userid_ids = fields.One2many( ebics_userid_ids = fields.One2many(
comodel_name='ebics.userid', comodel_name="ebics.userid",
inverse_name='ebics_config_id', inverse_name="ebics_config_id",
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
states={"draft": [("readonly", False)]},
help="Human users or a technical system that is/are " help="Human users or a technical system that is/are "
"assigned to a customer. " "assigned to a customer. "
"\nOn the EBICS bank server it is identified " "\nOn the EBICS bank server it is identified "
"by the combination of UserID and PartnerID. " "by the combination of UserID and PartnerID. "
"The technical subscriber serves only for the data exchange " "The technical subscriber serves only for the data exchange "
"between customer and financial institution. " "between customer and financial institution. "
"The human user also can authorise orders.") "The human user also can authorise orders.",
)
ebics_files = fields.Char( ebics_files = fields.Char(
string='EBICS Files Root', required=True, string="EBICS Files Root",
readonly=True, states={'draft': [('readonly', False)]}, required=True,
readonly=True,
states={"draft": [("readonly", False)]},
default=lambda self: self._default_ebics_files(), default=lambda self: self._default_ebics_files(),
help="Root Directory for EBICS File Transfer Folders.") help="Root Directory for EBICS File Transfer Folders.",
)
# We store the EBICS keys in a separate directory in the file system. # We store the EBICS keys in a separate directory in the file system.
# This directory requires special protection to reduce fraude. # This directory requires special protection to reduce fraude.
ebics_keys = fields.Char( ebics_keys = fields.Char(
string='EBICS Keys Root', required=True, string="EBICS Keys Root",
readonly=True, states={'draft': [('readonly', False)]}, required=True,
readonly=True,
states={"draft": [("readonly", False)]},
default=lambda self: self._default_ebics_keys(), default=lambda self: self._default_ebics_keys(),
help="Root Directory for storing the EBICS Keys.") help="Root Directory for storing the EBICS Keys.",
)
ebics_key_version = fields.Selection( ebics_key_version = fields.Selection(
selection=[('A005', 'A005 (RSASSA-PKCS1-v1_5)'), selection=[("A005", "A005 (RSASSA-PKCS1-v1_5)"), ("A006", "A006 (RSASSA-PSS)")],
('A006', 'A006 (RSASSA-PSS)')], string="EBICS key version",
string='EBICS key version', default="A006",
default='A006', readonly=True,
readonly=True, states={'draft': [('readonly', False)]}, states={"draft": [("readonly", False)]},
help="The key version of the electronic signature.") help="The key version of the electronic signature.",
)
ebics_key_bitlength = fields.Integer( ebics_key_bitlength = fields.Integer(
string='EBICS key bitlength', string="EBICS key bitlength",
default=2048, default=2048,
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
states={"draft": [("readonly", False)]},
help="The bit length of the generated keys. " help="The bit length of the generated keys. "
"\nThe value must be between 1536 and 4096.") "\nThe value must be between 1536 and 4096.",
)
ebics_file_format_ids = fields.Many2many( ebics_file_format_ids = fields.Many2many(
comodel_name='ebics.file.format', comodel_name="ebics.file.format",
column1='config_id', column2='format_id', column1="config_id",
string='EBICS File Formats', column2="format_id",
readonly=True, states={'draft': [('readonly', False)]}, string="EBICS File Formats",
readonly=True,
states={"draft": [("readonly", False)]},
) )
state = fields.Selection( state = fields.Selection(
[('draft', 'Draft'), [("draft", "Draft"), ("confirm", "Confirmed")],
('confirm', 'Confirmed')], string="State",
string='State', default="draft",
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_ids = fields.Many2many(
comodel_name='res.company',
string='Companies',
required=True, required=True,
help="Companies sharing this EBICS contract.") 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_ids = fields.Many2many(
comodel_name="res.company",
string="Companies",
required=True,
help="Companies sharing this EBICS contract.",
)
@api.model @api.model
def _default_ebics_files(self): def _default_ebics_files(self):
return '/'.join(['/home/odoo/ebics_files', self._cr.dbname]) return "/".join(["/home/odoo/ebics_files", self._cr.dbname])
@api.model @api.model
def _default_ebics_keys(self): def _default_ebics_keys(self):
return '/'.join(['/etc/odoo/ebics_keys', self._cr.dbname]) return "/".join(["/etc/odoo/ebics_keys", self._cr.dbname])
@api.constrains('order_number') @api.constrains("order_number")
def _check_order_number(self): def _check_order_number(self):
for cfg in self: for cfg in self:
nbr = cfg.order_number nbr = cfg.order_number
@ -140,26 +173,28 @@ class EbicsConfig(models.Model):
if not pattern.match(nbr): if not pattern.match(nbr):
ok = False ok = False
if not ok: if not ok:
raise UserError(_( raise UserError(
"Order Number should comply with the following pattern:" _(
"\n[A-Z]{1}[A-Z0-9]{3}")) "Order Number should comply with the following pattern:"
"\n[A-Z]{1}[A-Z0-9]{3}"
)
)
@api.onchange('journal_ids') @api.onchange("journal_ids")
def _onchange_journal_ids(self): def _onchange_journal_ids(self):
self.company_ids = self.journal_ids.mapped('company_id') self.company_ids = self.journal_ids.mapped("company_id")
def unlink(self): def unlink(self):
for ebics_config in self: for ebics_config in self:
if ebics_config.state == 'active': if ebics_config.state == "active":
raise UserError(_( raise UserError(_("You cannot remove active EBICS configurations."))
"You cannot remove active EBICS configurations."))
return super(EbicsConfig, self).unlink() return super(EbicsConfig, self).unlink()
def set_to_draft(self): def set_to_draft(self):
return self.write({'state': 'draft'}) return self.write({"state": "draft"})
def set_to_confirm(self): def set_to_confirm(self):
return self.write({'state': 'confirm'}) return self.write({"state": "confirm"})
def _get_order_number(self): def _get_order_number(self):
return self.order_number return self.order_number
@ -167,31 +202,37 @@ class EbicsConfig(models.Model):
def _update_order_number(self, OrderID): def _update_order_number(self, OrderID):
o_list = list(OrderID) o_list = list(OrderID)
for i, c in enumerate(reversed(o_list), start=1): for i, c in enumerate(reversed(o_list), start=1):
if c == '9': if c == "9":
o_list[-i] = 'A' o_list[-i] = "A"
break break
if c == 'Z': if c == "Z":
continue continue
else: else:
o_list[-i] = chr(ord(c) + 1) o_list[-i] = chr(ord(c) + 1)
break break
next = ''.join(o_list) next_order_number = "".join(o_list)
if next == 'ZZZZ': if next_order_number == "ZZZZ":
next = 'A000' next_order_number = "A000"
self.order_number = next self.order_number = next_order_number
def _check_ebics_keys(self): def _check_ebics_keys(self):
dirname = self.ebics_keys or '' dirname = self.ebics_keys or ""
if not os.path.exists(dirname): if not os.path.exists(dirname):
raise UserError(_( raise UserError(
"EBICS Keys Root Directory %s is not available." _(
"\nPlease contact your system administrator.") "EBICS Keys Root Directory %s is not available."
% dirname) "\nPlease contact your system administrator."
)
% dirname
)
def _check_ebics_files(self): def _check_ebics_files(self):
dirname = self.ebics_files or '' dirname = self.ebics_files or ""
if not os.path.exists(dirname): if not os.path.exists(dirname):
raise UserError(_( raise UserError(
"EBICS Files Root Directory %s is not available." _(
"\nPlease contact your system administrator.") "EBICS Files Root Directory %s is not available."
% dirname) "\nPlease contact your system administrator."
)
% dirname
)

View File

@ -1,4 +1,4 @@
# Copyright 2009-2021 Noviat. # Copyright 2009-2022 Noviat.
# License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl).
import base64 import base64
@ -6,113 +6,119 @@ import logging
from odoo import _, fields, models from odoo import _, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class EbicsFile(models.Model): class EbicsFile(models.Model):
_name = 'ebics.file' _name = "ebics.file"
_description = 'Object to store EBICS Data Files' _description = "Object to store EBICS Data Files"
_order = 'date desc' _order = "date desc"
_sql_constraints = [ _sql_constraints = [
('name_uniq', 'unique (name, format_id)', (
'This File has already been down- or uploaded !') "name_uniq",
"unique (name, format_id)",
"This File has already been down- or uploaded !",
)
] ]
name = fields.Char(string='Filename') name = fields.Char(string="Filename")
data = fields.Binary(string='File', readonly=True) data = fields.Binary(string="File", readonly=True)
format_id = fields.Many2one( format_id = fields.Many2one(
comodel_name='ebics.file.format', comodel_name="ebics.file.format", string="EBICS File Formats", readonly=True
string='EBICS File Formats', )
readonly=True) type = fields.Selection(related="format_id.type", readonly=True)
type = fields.Selection(
related='format_id.type',
readonly=True)
date_from = fields.Date( date_from = fields.Date(
readonly=True, readonly=True, help="'Date From' as entered in the download wizard."
help="'Date From' as entered in the download wizard.") )
date_to = fields.Date( date_to = fields.Date(
readonly=True, readonly=True, help="'Date To' as entered in the download wizard."
help="'Date To' as entered in the download wizard.") )
date = fields.Datetime( date = fields.Datetime(
required=True, readonly=True, required=True, readonly=True, help="File Upload/Download date"
help='File Upload/Download date') )
bank_statement_ids = fields.One2many( bank_statement_ids = fields.One2many(
comodel_name='account.bank.statement', comodel_name="account.bank.statement",
inverse_name='ebics_file_id', inverse_name="ebics_file_id",
string='Generated Bank Statements', readonly=True) string="Generated Bank Statements",
readonly=True,
)
state = fields.Selection( state = fields.Selection(
[('draft', 'Draft'), [("draft", "Draft"), ("done", "Done")],
('done', 'Done')], string="State",
string='State', default="draft",
default='draft', required=True,
required=True, readonly=True) readonly=True,
)
user_id = fields.Many2one( user_id = fields.Many2one(
comodel_name='res.users', string='User', comodel_name="res.users",
string="User",
default=lambda self: self.env.user, default=lambda self: self.env.user,
readonly=True) readonly=True,
)
ebics_userid_id = fields.Many2one( ebics_userid_id = fields.Many2one(
comodel_name='ebics.userid', comodel_name="ebics.userid",
string='EBICS UserID', string="EBICS UserID",
ondelete='restrict', ondelete="restrict",
readonly=True) readonly=True,
note = fields.Text(string='Notes') )
note_process = fields.Text(string='Notes') note = fields.Text(string="Notes")
note_process = fields.Text(string="Notes")
company_ids = fields.Many2many( company_ids = fields.Many2many(
comodel_name='res.company', comodel_name="res.company",
string='Companies', string="Companies",
help="Companies sharing this EBICS file.") help="Companies sharing this EBICS file.",
)
def unlink(self): def unlink(self):
ff_methods = self._file_format_methods() ff_methods = self._file_format_methods()
for ebics_file in self: for ebics_file in self:
if ebics_file.state == 'done': if ebics_file.state == "done":
raise UserError(_( raise UserError(_("You can only remove EBICS files in state 'Draft'."))
"You can only remove EBICS files in state 'Draft'."))
# execute format specific actions # execute format specific actions
ff = ebics_file.format_id.download_process_method ff = ebics_file.format_id.download_process_method
if ff in ff_methods: if ff in ff_methods:
if ff_methods[ff].get('unlink'): if ff_methods[ff].get("unlink"):
ff_methods[ff]['unlink'](ebics_file) ff_methods[ff]["unlink"](ebics_file)
# remove bank statements # remove bank statements
ebics_file.bank_statement_ids.unlink() ebics_file.bank_statement_ids.unlink()
return super(EbicsFile, self).unlink() return super(EbicsFile, self).unlink()
def set_to_draft(self): def set_to_draft(self):
return self.write({'state': 'draft'}) return self.write({"state": "draft"})
def set_to_done(self): def set_to_done(self):
return self.write({'state': 'done'}) return self.write({"state": "done"})
def process(self): def process(self):
self.ensure_one() self.ensure_one()
ctx = dict( ctx = dict(self.env.context, allowed_company_ids=self.env.user.company_ids.ids)
self.env.context,
allowed_company_ids=self.env.user.company_ids.ids)
self = self.with_context(ctx) self = self.with_context(ctx)
self.note_process = '' self.note_process = ""
ff_methods = self._file_format_methods() ff_methods = self._file_format_methods()
ff = self.format_id.download_process_method ff = self.format_id.download_process_method
if ff in ff_methods: if ff in ff_methods:
if ff_methods[ff].get('process'): if ff_methods[ff].get("process"):
res = ff_methods[ff]['process'](self) res = ff_methods[ff]["process"](self)
self.state = 'done' self.state = "done"
return res return res
else: else:
return self._process_undefined_format() return self._process_undefined_format()
def action_open_bank_statements(self): def action_open_bank_statements(self):
self.ensure_one() self.ensure_one()
action = self.env['ir.actions.act_window']._for_xml_id( action = self.env["ir.actions.act_window"]._for_xml_id(
'account.action_bank_statement_tree') "account.action_bank_statement_tree"
domain = eval(action.get('domain') or '[]') )
domain += [('id', 'in', self._context.get('statement_ids'))] domain = safe_eval(action.get("domain") or "[]")
action.update({'domain': domain}) domain += [("id", "in", self._context.get("statement_ids"))]
action.update({"domain": domain})
return action return action
def button_close(self): def button_close(self):
self.ensure_one() self.ensure_one()
return {'type': 'ir.actions.act_window_close'} return {"type": "ir.actions.act_window_close"}
def _file_format_methods(self): def _file_format_methods(self):
""" """
@ -120,35 +126,45 @@ class EbicsFile(models.Model):
for extra file formats. for extra file formats.
""" """
res = { res = {
'cfonb120': "cfonb120": {
{'process': self._process_cfonb120, "process": self._process_cfonb120,
'unlink': self._unlink_cfonb120}, "unlink": self._unlink_cfonb120,
'camt.052': },
{'process': self._process_camt052, "camt.052": {
'unlink': self._unlink_camt052}, "process": self._process_camt052,
'camt.053': "unlink": self._unlink_camt052,
{'process': self._process_camt053, },
'unlink': self._unlink_camt053}, "camt.053": {
'camt.054': "process": self._process_camt053,
{'process': self._process_camt054, "unlink": self._unlink_camt053,
'unlink': self._unlink_camt054}, },
'pain.002': "camt.054": {
{'process': self._process_pain002, "process": self._process_camt054,
'unlink': self._unlink_pain002}, "unlink": self._unlink_camt054,
},
"pain.002": {
"process": self._process_pain002,
"unlink": self._unlink_pain002,
},
} }
return res return res
def _check_import_module(self, module, raise_if_not_found=True): def _check_import_module(self, module, raise_if_not_found=True):
mod = self.env['ir.module.module'].sudo().search( mod = (
[('name', '=like', module), self.env["ir.module.module"]
('state', '=', 'installed')]) .sudo()
.search([("name", "=like", module), ("state", "=", "installed")])
)
if not mod: if not mod:
if raise_if_not_found: if raise_if_not_found:
raise UserError(_( raise UserError(
"The module to process the '%s' format is not installed " _(
"on your system. " "The module to process the '%s' format is not installed "
"\nPlease install module '%s'") "on your system. "
% (self.format_id.name, module)) "\nPlease install module '%s'"
)
% (self.format_id.name, module)
)
return False return False
return True return True
@ -156,19 +172,19 @@ class EbicsFile(models.Model):
notifications = [] notifications = []
st_line_ids = [] st_line_ids = []
statement_ids = [] statement_ids = []
if res.get('context'): if res.get("context"):
notifications = res['context'].get('notifications', []) notifications = res["context"].get("notifications", [])
st_line_ids = res['context'].get('statement_line_ids', []) st_line_ids = res["context"].get("statement_line_ids", [])
if notifications: if notifications:
for notif in notifications: for notif in notifications:
parts = [] parts = []
for k in ['type', 'message', 'details']: for k in ["type", "message", "details"]:
if notif.get(k): if notif.get(k):
msg = '%s: %s' % (k, notif[k]) msg = "{}: {}".format(k, notif[k])
parts.append(msg) parts.append(msg)
self.note_process += '\n'.join(parts) self.note_process += "\n".join(parts)
self.note_process += '\n' self.note_process += "\n"
self.note_process += '\n' self.note_process += "\n"
if st_line_ids: if st_line_ids:
self.flush() self.flush()
self.env.cr.execute( self.env.cr.execute(
@ -185,36 +201,37 @@ class EbicsFile(models.Model):
WHERE absl.id IN %s WHERE absl.id IN %s
ORDER BY date, company_id ORDER BY date, company_id
""", """,
(tuple(st_line_ids),) (tuple(st_line_ids),),
) )
sts_data = self.env.cr.dictfetchall() sts_data = self.env.cr.dictfetchall()
else: else:
sts_data = [] sts_data = []
st_cnt = len(sts_data) st_cnt = len(sts_data)
if st_cnt: if st_cnt:
self.note_process += _( self.note_process += _("%s bank statements have been imported: ") % st_cnt
"%s bank statements have been imported: " self.note_process += "\n"
) % st_cnt
self.note_process += '\n'
for st_data in sts_data: for st_data in sts_data:
self.note_process += ("\n%s, %s (%s)") % ( self.note_process += ("\n%s, %s (%s)") % (
st_data['date'], st_data['name'], st_data['company_name']) st_data["date"],
statement_ids = [x['statement_id'] for x in sts_data] st_data["name"],
st_data["company_name"],
)
statement_ids = [x["statement_id"] for x in sts_data]
if statement_ids: if statement_ids:
self.sudo().bank_statement_ids = [(6, 0, statement_ids)] self.sudo().bank_statement_ids = [(6, 0, statement_ids)]
ctx = dict(self.env.context, statement_ids=statement_ids) ctx = dict(self.env.context, statement_ids=statement_ids)
module = __name__.split('addons.')[1].split('.')[0] module = __name__.split("addons.")[1].split(".")[0]
result_view = self.env.ref('%s.ebics_file_view_form_result' % module) result_view = self.env.ref("%s.ebics_file_view_form_result" % module)
return { return {
'name': _('Import EBICS File'), "name": _("Import EBICS File"),
'res_id': self.id, "res_id": self.id,
'view_type': 'form', "view_type": "form",
'view_mode': 'form', "view_mode": "form",
'res_model': self._name, "res_model": self._name,
'view_id': result_view.id, "view_id": result_view.id,
'target': 'new', "target": "new",
'context': ctx, "context": ctx,
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
} }
@staticmethod @staticmethod
@ -223,60 +240,63 @@ class EbicsFile(models.Model):
We do not support the standard _journal_creation_wizard since a single We do not support the standard _journal_creation_wizard since a single
cfonb120 file may contain statements from different legal entities. cfonb120 file may contain statements from different legal entities.
""" """
import_module = 'account_statement_import_fr_cfonb' import_module = "account_statement_import_fr_cfonb"
self._check_import_module(import_module) self._check_import_module(import_module)
wiz_model = 'account.statement.import' wiz_model = "account.statement.import"
data_file = base64.b64decode(self.data) data_file = base64.b64decode(self.data)
lines = data_file.split(b'\n') lines = data_file.split(b"\n")
wiz_vals_list = [] wiz_vals_list = []
st_lines = b'' st_lines = b""
transactions = False transactions = False
for line in lines: for line in lines:
rec_type = line[0:2] rec_type = line[0:2]
acc_number = line[21:32] acc_number = line[21:32]
st_lines += line + b'\n' st_lines += line + b"\n"
if rec_type == b'04': if rec_type == b"04":
transactions = True transactions = True
if rec_type == b'07': if rec_type == b"07":
if transactions: if transactions:
fn = '_'.join([acc_number.decode(), self.name]) fn = "_".join([acc_number.decode(), self.name])
wiz_vals_list.append({ wiz_vals_list.append(
'statement_filename': fn, {
'statement_file': base64.b64encode(st_lines) "statement_filename": fn,
}) "statement_file": base64.b64encode(st_lines),
st_lines = b'' }
)
st_lines = b""
transactions = False transactions = False
result = { result = {
'type': 'ir.actions.client', "type": "ir.actions.client",
'tag': 'bank_statement_reconciliation_view', "tag": "bank_statement_reconciliation_view",
'context': {'statement_line_ids': [], "context": {
'company_ids': self.env.user.company_ids.ids, "statement_line_ids": [],
'notifications': []}, "company_ids": self.env.user.company_ids.ids,
"notifications": [],
},
} }
wiz_ctx = dict(self.env.context, active_model='ebics.file') wiz_ctx = dict(self.env.context, active_model="ebics.file")
for i, wiz_vals in enumerate(wiz_vals_list, start=1): for i, wiz_vals in enumerate(wiz_vals_list, start=1):
wiz = self.env[wiz_model].with_context(wiz_ctx).create(wiz_vals) wiz = self.env[wiz_model].with_context(wiz_ctx).create(wiz_vals)
res = wiz.import_file_button() res = wiz.import_file_button()
ctx = res.get('context') ctx = res.get("context")
if (res.get('res_model') if res.get("res_model") == "account.bank.statement.import.journal.creation":
== 'account.bank.statement.import.journal.creation'): message = _("Error detected while importing statement number %s.\n") % i
message = _(
"Error detected while importing statement number %s.\n"
) % i
message += _("No financial journal found.") message += _("No financial journal found.")
details = _( details = _("Bank account number: %s") % ctx.get(
'Bank account number: %s' "default_bank_acc_number"
) % ctx.get('default_bank_acc_number') )
result['context']['notifications'].extend([{ result["context"]["notifications"].extend(
'type': 'warning', [
'message': message, {
'details': details, "type": "warning",
}]) "message": message,
"details": details,
}
]
)
continue continue
result['context']['statement_line_ids'].extend( result["context"]["statement_line_ids"].extend(ctx["statement_line_ids"])
ctx['statement_line_ids']) result["context"]["notifications"].extend(ctx["notifications"])
result['context']['notifications'].extend(
ctx['notifications'])
return self._process_result_action(result) return self._process_result_action(result)
@staticmethod @staticmethod
@ -285,11 +305,10 @@ class EbicsFile(models.Model):
Placeholder for cfonb120 specific actions before removing the Placeholder for cfonb120 specific actions before removing the
EBICS data file and its related bank statements. EBICS data file and its related bank statements.
""" """
pass
@staticmethod @staticmethod
def _process_camt052(self): def _process_camt052(self):
import_module = 'account_statement_import_camt' import_module = "account_statement_import_camt"
self._check_import_module(import_module) self._check_import_module(import_module)
return self._process_camt053(self) return self._process_camt053(self)
@ -299,11 +318,10 @@ class EbicsFile(models.Model):
Placeholder for camt052 specific actions before removing the Placeholder for camt052 specific actions before removing the
EBICS data file and its related bank statements. EBICS data file and its related bank statements.
""" """
pass
@staticmethod @staticmethod
def _process_camt054(self): def _process_camt054(self):
import_module = 'account_statement_import_camt' import_module = "account_statement_import_camt"
self._check_import_module(import_module) self._check_import_module(import_module)
return self._process_camt053(self) return self._process_camt053(self)
@ -313,84 +331,89 @@ class EbicsFile(models.Model):
Placeholder for camt054 specific actions before removing the Placeholder for camt054 specific actions before removing the
EBICS data file and its related bank statements. EBICS data file and its related bank statements.
""" """
pass
@staticmethod @staticmethod
def _process_camt053(self): def _process_camt053(self):
modules = [ modules = [
('oca', 'account_statement_import_camt'), ("oca", "account_statement_import_camt"),
('oe', 'account_bank_statement_import_camt'), ("oe", "account_bank_statement_import_camt"),
] ]
found = False found = False
for src, mod in modules: for _src, mod in modules:
if self._check_import_module(mod, raise_if_not_found=False): if self._check_import_module(mod, raise_if_not_found=False):
found = True found = True
break break
if not found: if not found:
raise UserError(_( raise UserError(
"The module to process the '%s' format is not installed " _(
"on your system. " "The module to process the '%s' format is not installed "
"\nPlease install one of the following modules: \n%s." "on your system. "
) % (self.format_id.name, ', '.join([x[1] for x in modules])) "\nPlease install one of the following modules: \n%s."
)
% (self.format_id.name, ", ".join([x[1] for x in modules]))
) )
if src == 'oca': if _src == "oca":
self._process_camt053_oca() self._process_camt053_oca()
else: else:
self._process_camt053_oe() self._process_camt053_oe()
def _process_camt053_oca(self): def _process_camt053_oca(self):
wiz_model = 'account.statement.import' wiz_model = "account.statement.import"
wiz_vals = { wiz_vals = {
'statement_filename': self.name, "statement_filename": self.name,
'statement_file': self.data, "statement_file": self.data,
} }
result = { result = {
'type': 'ir.actions.client', "type": "ir.actions.client",
'tag': 'bank_statement_reconciliation_view', "tag": "bank_statement_reconciliation_view",
'context': {'statement_line_ids': [], "context": {
'company_ids': self.env.user.company_ids.ids, "statement_line_ids": [],
'notifications': []}, "company_ids": self.env.user.company_ids.ids,
"notifications": [],
},
} }
wiz_ctx = dict(self.env.context, active_model='ebics.file') wiz_ctx = dict(self.env.context, active_model="ebics.file")
wiz = self.env[wiz_model].with_context(wiz_ctx).create(wiz_vals) wiz = self.env[wiz_model].with_context(wiz_ctx).create(wiz_vals)
res = wiz.import_file_button() res = wiz.import_file_button()
ctx = res.get('context') ctx = res.get("context")
if (res.get('res_model') if res.get("res_model") == "account.bank.statement.import.journal.creation":
== 'account.bank.statement.import.journal.creation'): message = _("Error detected while importing statement %s.\n") % self.name
message = _(
"Error detected while importing statement %s.\n"
) % self.name
message += _("No financial journal found.") message += _("No financial journal found.")
details = _( details = _("Bank account number: %s") % ctx.get("default_bank_acc_number")
'Bank account number: %s' result["context"]["notifications"].extend(
) % ctx.get('default_bank_acc_number') [
result['context']['notifications'].extend([{ {
'type': 'warning', "type": "warning",
'message': message, "message": message,
'details': details, "details": details,
}]) }
result['context']['statement_line_ids'].extend( ]
ctx['statement_line_ids']) )
result['context']['notifications'].extend( result["context"]["statement_line_ids"].extend(ctx["statement_line_ids"])
ctx['notifications']) result["context"]["notifications"].extend(ctx["notifications"])
return self._process_result_action(result) return self._process_result_action(result)
def _process_camt053_oe(self): def _process_camt053_oe(self):
wiz_model = 'account.bank.statement.import' wiz_model = "account.bank.statement.import"
wiz_vals = { wiz_vals = {
'attachment_ids': [(0, 0, {'name': self.name, "attachment_ids": [
'datas': self.data, (
'store_fname': self.name})]} 0,
ctx = dict(self.env.context, active_model='ebics.file') 0,
{"name": self.name, "datas": self.data, "store_fname": self.name},
)
]
}
ctx = dict(self.env.context, active_model="ebics.file")
wiz = self.env[wiz_model].with_context(ctx).create(wiz_vals) wiz = self.env[wiz_model].with_context(ctx).create(wiz_vals)
res = wiz.import_file() res = wiz.import_file()
if res.get('res_model') \ if res.get("res_model") == "account.bank.statement.import.journal.creation":
== 'account.bank.statement.import.journal.creation': if res.get("context"):
if res.get('context'): bank_account = res["context"].get("default_bank_acc_number")
bank_account = res['context'].get('default_bank_acc_number') raise UserError(
raise UserError(_( _("No financial journal found for Company Bank Account %s")
"No financial journal found for Company Bank Account %s" % bank_account
) % bank_account) )
return self._process_result_action(res) return self._process_result_action(res)
@staticmethod @staticmethod
@ -399,7 +422,6 @@ class EbicsFile(models.Model):
Placeholder for camt053 specific actions before removing the Placeholder for camt053 specific actions before removing the
EBICS data file and its related bank statements. EBICS data file and its related bank statements.
""" """
pass
@staticmethod @staticmethod
def _process_pain002(self): def _process_pain002(self):
@ -408,7 +430,6 @@ class EbicsFile(models.Model):
TODO: TODO:
add import logic based upon OCA 'account_payment_return_import' add import logic based upon OCA 'account_payment_return_import'
""" """
pass
@staticmethod @staticmethod
def _unlink_pain002(self): def _unlink_pain002(self):
@ -419,8 +440,11 @@ class EbicsFile(models.Model):
raise NotImplementedError raise NotImplementedError
def _process_undefined_format(self): def _process_undefined_format(self):
raise UserError(_( raise UserError(
"The current version of the 'account_ebics' module " _(
"has no support to automatically process EBICS files " "The current version of the 'account_ebics' module "
"with format %s." "has no support to automatically process EBICS files "
) % self.format_id.name) "with format %s."
)
% self.format_id.name
)

View File

@ -5,58 +5,60 @@ from odoo import api, fields, models
class EbicsFileFormat(models.Model): class EbicsFileFormat(models.Model):
_name = 'ebics.file.format' _name = "ebics.file.format"
_description = 'EBICS File Formats' _description = "EBICS File Formats"
_order = 'type,name,order_type' _order = "type,name,order_type"
name = fields.Char( name = fields.Char(
string='Request Type', string="Request Type",
required=True, required=True,
help="E.g. camt.xxx.cfonb120.stm, pain.001.001.03.sct.\n" help="E.g. camt.xxx.cfonb120.stm, pain.001.001.03.sct.\n"
"Specify camt.052, camt.053, camt.054 for camt " "Specify camt.052, camt.053, camt.054 for camt "
"Order Types such as C53, Z53, C54, Z54.\n" "Order Types such as C53, Z53, C54, Z54.\n"
"This name has to match the 'Request Type' in your " "This name has to match the 'Request Type' in your "
"EBICS contract for Order Type 'FDL' or 'FUL'.\n") "EBICS contract for Order Type 'FDL' or 'FUL'.\n",
)
type = fields.Selection( type = fields.Selection(
selection=[('down', 'Download'), selection=[("down", "Download"), ("up", "Upload")], required=True
('up', 'Upload')], )
required=True)
order_type = fields.Char( order_type = fields.Char(
string='Order Type', string="Order Type",
required=True, required=True,
help="E.g. C53 (check your EBICS contract).\n" help="E.g. C53 (check your EBICS contract).\n"
"For most banks in France you should use the " "For most banks in France you should use the "
"format neutral Order Types 'FUL' for upload " "format neutral Order Types 'FUL' for upload "
"and 'FDL' for download.") "and 'FDL' for download.",
)
download_process_method = fields.Selection( download_process_method = fields.Selection(
selection='_selection_download_process_method', selection="_selection_download_process_method",
help="Enable processing within Odoo of the downloaded file " help="Enable processing within Odoo of the downloaded file "
"via the 'Process' button." "via the 'Process' button."
"E.g. specify camt.053 to import a camt.053 file and create " "E.g. specify camt.053 to import a camt.053 file and create "
"a bank statement.") "a bank statement.",
)
# TODO: # TODO:
# move signature_class parameter so that it can be set per EBICS config # move signature_class parameter so that it can be set per EBICS config
signature_class = fields.Selection( signature_class = fields.Selection(
selection=[('E', 'Single signature'), selection=[("E", "Single signature"), ("T", "Transport signature")],
('T', 'Transport signature')], string="Signature Class",
string='Signature Class',
help="Please doublecheck the security of your Odoo " help="Please doublecheck the security of your Odoo "
"ERP system when using class 'E' to prevent unauthorised " "ERP system when using class 'E' to prevent unauthorised "
"users to make supplier payments." "users to make supplier payments."
"\nLeave this field empty to use the default " "\nLeave this field empty to use the default "
"defined for your EBICS UserID.") "defined for your EBICS UserID.",
)
description = fields.Char() description = fields.Char()
suffix = fields.Char( suffix = fields.Char(
required=True, required=True,
help="Specify the filename suffix for this File Format." help="Specify the filename suffix for this File Format." "\nE.g. c53.xml",
"\nE.g. c53.xml") )
@api.model @api.model
def _selection_download_process_method(self): def _selection_download_process_method(self):
methods = self.env['ebics.file']._file_format_methods().keys() methods = self.env["ebics.file"]._file_format_methods().keys()
return [(x, x) for x in methods] return [(x, x) for x in methods]
@api.onchange('type') @api.onchange("type")
def _onchange_type(self): def _onchange_type(self):
if self.type == 'up': if self.type == "up":
self.download_process_method = False self.download_process_method = False

View File

@ -13,224 +13,240 @@ from odoo.exceptions import UserError
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
# logging.basicConfig(
""" # level=logging.DEBUG,
logging.basicConfig( # format='[%(asctime)s] %(levelname)s - %(name)s: %(message)s')
level=logging.DEBUG,
format='[%(asctime)s] %(levelname)s - %(name)s: %(message)s')
"""
try: try:
import fintech import fintech
from fintech.ebics import EbicsKeyRing, EbicsBank, EbicsUser,\ from fintech.ebics import (
EbicsClient, EbicsFunctionalError, EbicsTechnicalError EbicsBank,
fintech.cryptolib = 'cryptography' EbicsClient,
EbicsFunctionalError,
EbicsKeyRing,
EbicsTechnicalError,
EbicsUser,
)
fintech.cryptolib = "cryptography"
except ImportError: except ImportError:
_logger.warning('Failed to import fintech') _logger.warning("Failed to import fintech")
class EbicsBank(EbicsBank): class EbicsBank(EbicsBank):
def _next_order_id(self, partnerid): def _next_order_id(self, partnerid):
""" """
EBICS protocol version H003 requires generation of the OrderID. EBICS protocol version H003 requires generation of the OrderID.
The OrderID must be a string between 'A000' and 'ZZZZ' and The OrderID must be a string between 'A000' and 'ZZZZ' and
unique for each partner id. unique for each partner id.
""" """
return hasattr(self, '_order_number') and self._order_number or 'A000' return hasattr(self, "_order_number") and self._order_number or "A000"
class EbicsUserID(models.Model): class EbicsUserID(models.Model):
_name = 'ebics.userid' _name = "ebics.userid"
_description = 'EBICS UserID' _description = "EBICS UserID"
_order = 'name' _order = "name"
name = fields.Char( name = fields.Char(
string='EBICS UserID', required=True, string="EBICS UserID",
readonly=True, states={'draft': [('readonly', False)]}, required=True,
readonly=True,
states={"draft": [("readonly", False)]},
help="Human users or a technical system that is/are " help="Human users or a technical system that is/are "
"assigned to a customer. " "assigned to a customer. "
"\nOn the EBICS bank server it is identified " "\nOn the EBICS bank server it is identified "
"by the combination of UserID and PartnerID. " "by the combination of UserID and PartnerID. "
"The technical subscriber serves only for the data exchange " "The technical subscriber serves only for the data exchange "
"between customer and financial institution. " "between customer and financial institution. "
"The human user also can authorise orders.") "The human user also can authorise orders.",
)
ebics_config_id = fields.Many2one( ebics_config_id = fields.Many2one(
comodel_name='ebics.config', comodel_name="ebics.config", string="EBICS Configuration", ondelete="cascade"
string='EBICS Configuration', )
ondelete='cascade')
user_ids = fields.Many2many( user_ids = fields.Many2many(
comodel_name='res.users', comodel_name="res.users",
string='Users', string="Users",
required=True, required=True,
help="Users who are allowed to use this EBICS UserID for " help="Users who are allowed to use this EBICS UserID for "
" bank transactions.") " bank transactions.",
)
# Currently only a singe signature class per user is supported # Currently only a singe signature class per user is supported
# Classes A and B are not yet supported. # Classes A and B are not yet supported.
signature_class = fields.Selection( signature_class = fields.Selection(
selection=[('E', 'Single signature'), selection=[("E", "Single signature"), ("T", "Transport signature")],
('T', 'Transport signature')], string="Signature Class",
string='Signature Class', required=True,
required=True, default='T', default="T",
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
states={"draft": [("readonly", False)]},
help="Default signature class." help="Default signature class."
"This default can be overriden for specific " "This default can be overriden for specific "
"EBICS transactions (cf. File Formats).") "EBICS transactions (cf. File Formats).",
ebics_keys_fn = fields.Char( )
compute='_compute_ebics_keys_fn') ebics_keys_fn = fields.Char(compute="_compute_ebics_keys_fn")
ebics_keys_found = fields.Boolean( ebics_keys_found = fields.Boolean(compute="_compute_ebics_keys_found")
compute='_compute_ebics_keys_found') ebics_passphrase = fields.Char(string="EBICS Passphrase")
ebics_passphrase = fields.Char(
string='EBICS Passphrase')
ebics_ini_letter = fields.Binary( ebics_ini_letter = fields.Binary(
string='EBICS INI Letter', readonly=True, string="EBICS INI Letter",
help="INI-letter PDF document to be sent to your bank.") readonly=True,
ebics_ini_letter_fn = fields.Char( help="INI-letter PDF document to be sent to your bank.",
string='INI-letter Filename', readonly=True) )
ebics_ini_letter_fn = fields.Char(string="INI-letter Filename", readonly=True)
ebics_public_bank_keys = fields.Binary( ebics_public_bank_keys = fields.Binary(
string='EBICS Public Bank Keys', readonly=True, string="EBICS Public Bank Keys",
help="EBICS Public Bank Keys to be checked for consistency.") readonly=True,
help="EBICS Public Bank Keys to be checked for consistency.",
)
ebics_public_bank_keys_fn = fields.Char( ebics_public_bank_keys_fn = fields.Char(
string='EBICS Public Bank Keys Filename', readonly=True) string="EBICS Public Bank Keys Filename", readonly=True
)
swift_3skey = fields.Boolean( swift_3skey = fields.Boolean(
string='Enable 3SKey support', string="Enable 3SKey support",
help="Transactions for this user will be signed " help="Transactions for this user will be signed "
"by means of the SWIFT 3SKey token.") "by means of the SWIFT 3SKey token.",
swift_3skey_certificate = fields.Binary( )
string='3SKey Certficate') swift_3skey_certificate = fields.Binary(string="3SKey Certficate")
swift_3skey_certificate_fn = fields.Char( swift_3skey_certificate_fn = fields.Char(string="EBICS Public Bank Keys Filename")
string='EBICS Public Bank Keys Filename')
# X.509 Distinguished Name attributes used to # X.509 Distinguished Name attributes used to
# create self-signed X.509 certificates # create self-signed X.509 certificates
ebics_key_x509 = fields.Boolean( ebics_key_x509 = fields.Boolean(
string='X509 support', string="X509 support",
help="Set this flag in order to work with " help="Set this flag in order to work with " "self-signed X.509 certificates",
"self-signed X.509 certificates") )
ebics_key_x509_dn_cn = fields.Char( ebics_key_x509_dn_cn = fields.Char(
string='Common Name [CN]', string="Common Name [CN]",
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
states={"draft": [("readonly", False)]},
) )
ebics_key_x509_dn_o = fields.Char( ebics_key_x509_dn_o = fields.Char(
string='Organization Name [O]', string="Organization Name [O]",
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
states={"draft": [("readonly", False)]},
) )
ebics_key_x509_dn_ou = fields.Char( ebics_key_x509_dn_ou = fields.Char(
string='Organizational Unit Name [OU]', string="Organizational Unit Name [OU]",
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
states={"draft": [("readonly", False)]},
) )
ebics_key_x509_dn_c = fields.Char( ebics_key_x509_dn_c = fields.Char(
string='Country Name [C]', string="Country Name [C]",
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
states={"draft": [("readonly", False)]},
) )
ebics_key_x509_dn_st = fields.Char( ebics_key_x509_dn_st = fields.Char(
string='State Or Province Name [ST]', string="State Or Province Name [ST]",
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
states={"draft": [("readonly", False)]},
) )
ebics_key_x509_dn_l = fields.Char( ebics_key_x509_dn_l = fields.Char(
string='Locality Name [L]', string="Locality Name [L]",
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
states={"draft": [("readonly", False)]},
) )
ebics_key_x509_dn_e = fields.Char( ebics_key_x509_dn_e = fields.Char(
string='Email Address', string="Email Address",
readonly=True, states={'draft': [('readonly', False)]}, readonly=True,
states={"draft": [("readonly", False)]},
) )
state = fields.Selection( state = fields.Selection(
[('draft', 'Draft'), [
('init', 'Initialisation'), ("draft", "Draft"),
('get_bank_keys', 'Get Keys from Bank'), ("init", "Initialisation"),
('to_verify', 'Verification'), ("get_bank_keys", "Get Keys from Bank"),
('active_keys', 'Active Keys')], ("to_verify", "Verification"),
string='State', ("active_keys", "Active Keys"),
default='draft', ],
required=True, readonly=True) string="State",
active = fields.Boolean( default="draft",
string='Active', default=True)
company_ids = fields.Many2many(
comodel_name='res.company',
string='Companies',
required=True, required=True,
help="Companies sharing this EBICS contract.") readonly=True,
)
active = fields.Boolean(string="Active", default=True)
company_ids = fields.Many2many(
comodel_name="res.company",
string="Companies",
required=True,
help="Companies sharing this EBICS contract.",
)
@api.depends('name') @api.depends("name")
def _compute_ebics_keys_fn(self): def _compute_ebics_keys_fn(self):
for rec in self: for rec in self:
keys_dir = rec.ebics_config_id.ebics_keys keys_dir = rec.ebics_config_id.ebics_keys
rec.ebics_keys_fn = ( rec.ebics_keys_fn = (
rec.name rec.name and keys_dir and (keys_dir + "/" + rec.name + "_keys")
and keys_dir
and (keys_dir + '/' + rec.name + '_keys'))
@api.depends('ebics_keys_fn')
def _compute_ebics_keys_found(self):
for rec in self:
rec.ebics_keys_found = (
rec.ebics_keys_fn
and os.path.isfile(rec.ebics_keys_fn)
) )
@api.constrains('ebics_passphrase') @api.depends("ebics_keys_fn")
def _compute_ebics_keys_found(self):
for rec in self:
rec.ebics_keys_found = rec.ebics_keys_fn and os.path.isfile(
rec.ebics_keys_fn
)
@api.constrains("ebics_passphrase")
def _check_ebics_passphrase(self): def _check_ebics_passphrase(self):
for rec in self: for rec in self:
if not rec.ebics_passphrase or len(rec.ebics_passphrase) < 8: if not rec.ebics_passphrase or len(rec.ebics_passphrase) < 8:
raise UserError(_( raise UserError(_("The passphrase must be at least 8 characters long"))
"The passphrase must be at least 8 characters long"))
@api.onchange('signature_class') @api.onchange("signature_class")
def _onchange_signature_class(self): def _onchange_signature_class(self):
if self.signature_class == 'T': if self.signature_class == "T":
self.swift_3skey = False self.swift_3skey = False
@api.onchange('swift_3skey') @api.onchange("swift_3skey")
def _onchange_swift_3skey(self): def _onchange_swift_3skey(self):
if self.swift_3skey: if self.swift_3skey:
self.ebics_key_x509 = True self.ebics_key_x509 = True
def set_to_draft(self): def set_to_draft(self):
return self.write({'state': 'draft'}) return self.write({"state": "draft"})
def set_to_active_keys(self): def set_to_active_keys(self):
return self.write({'state': 'active_keys'}) return self.write({"state": "active_keys"})
def set_to_get_bank_keys(self): def set_to_get_bank_keys(self):
return self.write({'state': 'get_bank_keys'}) return self.write({"state": "get_bank_keys"})
def ebics_init_1(self): def ebics_init_1(self): # noqa: C901
""" """
Initialization of bank keys - Step 1: Initialization of bank keys - Step 1:
Create new keys and certificates for this user Create new keys and certificates for this user
""" """
self.ensure_one() self.ensure_one()
self.ebics_config_id._check_ebics_files() self.ebics_config_id._check_ebics_files()
if self.state != 'draft': if self.state != "draft":
raise UserError( raise UserError(
_("Set state to 'draft' before Bank Key (re)initialisation.")) _("Set state to 'draft' before Bank Key (re)initialisation.")
)
if not self.ebics_passphrase: if not self.ebics_passphrase:
raise UserError( raise UserError(_("Set a passphrase."))
_("Set a passphrase."))
if self.swift_3skey and not self.swift_3skey_certificate: if self.swift_3skey and not self.swift_3skey_certificate:
raise UserError( raise UserError(_("3SKey certificate missing."))
_("3SKey certificate missing."))
ebics_version = self.ebics_config_id.ebics_version ebics_version = self.ebics_config_id.ebics_version
try: try:
keyring = EbicsKeyRing( keyring = EbicsKeyRing(
keys=self.ebics_keys_fn, keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase
passphrase=self.ebics_passphrase) )
bank = EbicsBank( bank = EbicsBank(
keyring=keyring, keyring=keyring,
hostid=self.ebics_config_id.ebics_host, hostid=self.ebics_config_id.ebics_host,
url=self.ebics_config_id.ebics_url) url=self.ebics_config_id.ebics_url,
)
user = EbicsUser( user = EbicsUser(
keyring=keyring, keyring=keyring,
partnerid=self.ebics_config_id.ebics_partner, partnerid=self.ebics_config_id.ebics_partner,
userid=self.name) userid=self.name,
)
except Exception: except Exception:
exctype, value = exc_info()[:2] exctype, value = exc_info()[:2]
error = _("EBICS Initialisation Error:") error = _("EBICS Initialisation Error:")
error += '\n' + str(exctype) + '\n' + str(value) error += "\n" + str(exctype) + "\n" + str(value)
raise UserError(error) raise UserError(error)
self.ebics_config_id._check_ebics_keys() self.ebics_config_id._check_ebics_keys()
@ -240,60 +256,63 @@ class EbicsUserID(models.Model):
# enable import of all type of certicates: A00x, X002, E002 # enable import of all type of certicates: A00x, X002, E002
if self.swift_3skey: if self.swift_3skey:
kwargs = { kwargs = {
self.ebics_config_id.ebics_key_version: self.ebics_config_id.ebics_key_version: base64.decodestring(
base64.decodestring(self.swift_3skey_certificate), self.swift_3skey_certificate
),
} }
user.import_certificates(**kwargs) user.import_certificates(**kwargs)
user.create_keys( user.create_keys(
keyversion=self.ebics_config_id.ebics_key_version, keyversion=self.ebics_config_id.ebics_key_version,
bitlength=self.ebics_config_id.ebics_key_bitlength) bitlength=self.ebics_config_id.ebics_key_bitlength,
)
except Exception: except Exception:
exctype, value = exc_info()[:2] exctype, value = exc_info()[:2]
error = _("EBICS Initialisation Error:") error = _("EBICS Initialisation Error:")
error += '\n' + str(exctype) + '\n' + str(value) error += "\n" + str(exctype) + "\n" + str(value)
raise UserError(error) raise UserError(error)
if self.swift_3skey and not self.ebics_key_x509: if self.swift_3skey and not self.ebics_key_x509:
raise UserError(_( raise UserError(
"The current version of this module " _(
"requires to X509 support when enabling 3SKey")) "The current version of this module "
"requires to X509 support when enabling 3SKey"
)
)
if self.ebics_key_x509: if self.ebics_key_x509:
dn_attrs = { dn_attrs = {
'commonName': self.ebics_key_x509_dn_cn, "commonName": self.ebics_key_x509_dn_cn,
'organizationName': self.ebics_key_x509_dn_o, "organizationName": self.ebics_key_x509_dn_o,
'organizationalUnitName': self.ebics_key_x509_dn_ou, "organizationalUnitName": self.ebics_key_x509_dn_ou,
'countryName': self.ebics_key_x509_dn_c, "countryName": self.ebics_key_x509_dn_c,
'stateOrProvinceName': self.ebics_key_x509_dn_st, "stateOrProvinceName": self.ebics_key_x509_dn_st,
'localityName': self.ebics_key_x509_dn_l, "localityName": self.ebics_key_x509_dn_l,
'emailAddress': self.ebics_key_x509_dn_e, "emailAddress": self.ebics_key_x509_dn_e,
} }
kwargs = {k: v for k, v in dn_attrs.items() if v} kwargs = {k: v for k, v in dn_attrs.items() if v}
user.create_certificates(**kwargs) user.create_certificates(**kwargs)
client = EbicsClient( client = EbicsClient(bank, user, version=ebics_version)
bank, user, version=ebics_version)
# Send the public electronic signature key to the bank. # Send the public electronic signature key to the bank.
ebics_config_bank = self.ebics_config_id.journal_ids[0].bank_id ebics_config_bank = self.ebics_config_id.journal_ids[0].bank_id
if not ebics_config_bank: if not ebics_config_bank:
raise UserError(_( raise UserError(
"No bank defined for the financial journal " _("No bank defined for the financial journal " "of the EBICS Config")
"of the EBICS Config")) )
try: try:
supported_versions = client.HEV() supported_versions = client.HEV()
if ebics_version not in supported_versions: if ebics_version not in supported_versions:
err_msg = _("EBICS version mismatch.") + "\n" err_msg = _("EBICS version mismatch.") + "\n"
err_msg += _("Versions supported by your bank:") err_msg += _("Versions supported by your bank:")
for k in supported_versions: for k in supported_versions:
err_msg += "\n%s: %s " % (k, supported_versions[k]) err_msg += "\n{}: {} ".format(k, supported_versions[k])
raise UserError(err_msg) raise UserError(err_msg)
if ebics_version == 'H003': if ebics_version == "H003":
bank._order_number = self.ebics_config_id._get_order_number() bank._order_number = self.ebics_config_id._get_order_number()
OrderID = client.INI() OrderID = client.INI()
_logger.info( _logger.info("%s, EBICS INI command, OrderID=%s", self._name, OrderID)
'%s, EBICS INI command, OrderID=%s', self._name, OrderID) if ebics_version == "H003":
if ebics_version == 'H003':
self.ebics_config_id._update_order_number(OrderID) self.ebics_config_id._update_order_number(OrderID)
except URLError: except URLError:
exctype, value = exc_info()[:2] exctype, value = exc_info()[:2]
@ -303,69 +322,68 @@ class EbicsUserID(models.Model):
self.name, self.name,
tb, tb,
) )
raise UserError(_( raise UserError(
"urlopen error:\n url '%s' - %s") _("urlopen error:\n url '%s' - %s")
% (self.ebics_config_id.ebics_url, str(value))) % (self.ebics_config_id.ebics_url, str(value))
)
except EbicsFunctionalError: except EbicsFunctionalError:
e = exc_info() e = exc_info()
error = _("EBICS Functional Error:") error = _("EBICS Functional Error:")
error += '\n' error += "\n"
error += '%s (code: %s)' % (e[1].message, e[1].code) error += "{} (code: {})".format(e[1].message, e[1].code)
raise UserError(error) raise UserError(error)
except EbicsTechnicalError: except EbicsTechnicalError:
e = exc_info() e = exc_info()
error = _("EBICS Technical Error:") error = _("EBICS Technical Error:")
error += '\n' error += "\n"
error += '%s (code: %s)' % (e[1].message, e[1].code) error += "{} (code: {})".format(e[1].message, e[1].code)
raise UserError(error) raise UserError(error)
# Send the public authentication and encryption keys to the bank. # Send the public authentication and encryption keys to the bank.
if ebics_version == 'H003': if ebics_version == "H003":
bank._order_number = self.ebics_config_id._get_order_number() bank._order_number = self.ebics_config_id._get_order_number()
OrderID = client.HIA() OrderID = client.HIA()
_logger.info('%s, EBICS HIA command, OrderID=%s', self._name, OrderID) _logger.info("%s, EBICS HIA command, OrderID=%s", self._name, OrderID)
if ebics_version == 'H003': if ebics_version == "H003":
self.ebics_config_id._update_order_number(OrderID) self.ebics_config_id._update_order_number(OrderID)
# Create an INI-letter which must be printed and sent to the bank. # Create an INI-letter which must be printed and sent to the bank.
ebics_config_bank = self.ebics_config_id.journal_ids[0].bank_id ebics_config_bank = self.ebics_config_id.journal_ids[0].bank_id
cc = ebics_config_bank.country.code cc = ebics_config_bank.country.code
if cc in ['FR', 'DE']: if cc in ["FR", "DE"]:
lang = cc lang = cc
else: else:
lang = self.env.user.lang or \ lang = self.env.user.lang or self.env["res.lang"].search([])[0].code
self.env['res.lang'].search([])[0].code
lang = lang[:2] lang = lang[:2]
tmp_dir = os.path.normpath(self.ebics_config_id.ebics_files + '/tmp') tmp_dir = os.path.normpath(self.ebics_config_id.ebics_files + "/tmp")
if not os.path.isdir(tmp_dir): if not os.path.isdir(tmp_dir):
os.makedirs(tmp_dir, mode=0o700) os.makedirs(tmp_dir, mode=0o700)
fn_date = fields.Date.today().isoformat() fn_date = fields.Date.today().isoformat()
fn = '_'.join( fn = "_".join([self.ebics_config_id.ebics_host, "ini_letter", fn_date]) + ".pdf"
[self.ebics_config_id.ebics_host, 'ini_letter', fn_date]) + '.pdf' full_tmp_fn = os.path.normpath(tmp_dir + "/" + fn)
full_tmp_fn = os.path.normpath(tmp_dir + '/' + fn)
user.create_ini_letter( user.create_ini_letter(
bankname=ebics_config_bank.name, bankname=ebics_config_bank.name, path=full_tmp_fn, lang=lang
path=full_tmp_fn, )
lang=lang) with open(full_tmp_fn, "rb") as f:
with open(full_tmp_fn, 'rb') as f:
letter = f.read() letter = f.read()
self.write({ self.write(
'ebics_ini_letter': base64.encodebytes(letter), {
'ebics_ini_letter_fn': fn, "ebics_ini_letter": base64.encodebytes(letter),
}) "ebics_ini_letter_fn": fn,
}
)
return self.write({'state': 'init'}) return self.write({"state": "init"})
def ebics_init_2(self): def ebics_init_2(self):
""" """
Initialization of bank keys - Step 2: Initialization of bank keys - Step 2:
Activation of the account by the bank. Activation of the account by the bank.
""" """
if self.state != 'init': if self.state != "init":
raise UserError( raise UserError(_("Set state to 'Initialisation'."))
_("Set state to 'Initialisation'."))
self.ensure_one() self.ensure_one()
return self.write({'state': 'get_bank_keys'}) return self.write({"state": "get_bank_keys"})
def ebics_init_3(self): def ebics_init_3(self):
""" """
@ -376,26 +394,27 @@ class EbicsUserID(models.Model):
""" """
self.ensure_one() self.ensure_one()
self.ebics_config_id._check_ebics_files() self.ebics_config_id._check_ebics_files()
if self.state != 'get_bank_keys': if self.state != "get_bank_keys":
raise UserError( raise UserError(_("Set state to 'Get Keys from Bank'."))
_("Set state to 'Get Keys from Bank'."))
try: try:
keyring = EbicsKeyRing( keyring = EbicsKeyRing(
keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase) keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase
)
bank = EbicsBank( bank = EbicsBank(
keyring=keyring, keyring=keyring,
hostid=self.ebics_config_id.ebics_host, hostid=self.ebics_config_id.ebics_host,
url=self.ebics_config_id.ebics_url) url=self.ebics_config_id.ebics_url,
)
user = EbicsUser( user = EbicsUser(
keyring=keyring, keyring=keyring,
partnerid=self.ebics_config_id.ebics_partner, partnerid=self.ebics_config_id.ebics_partner,
userid=self.name) userid=self.name,
client = EbicsClient( )
bank, user, version=self.ebics_config_id.ebics_version) client = EbicsClient(bank, user, version=self.ebics_config_id.ebics_version)
except Exception: except Exception:
exctype, value = exc_info()[:2] exctype, value = exc_info()[:2]
error = _("EBICS Initialisation Error:") error = _("EBICS Initialisation Error:")
error += '\n' + str(exctype) + '\n' + str(value) error += "\n" + str(exctype) + "\n" + str(value)
raise UserError(error) raise UserError(error)
try: try:
@ -403,28 +422,31 @@ class EbicsUserID(models.Model):
except EbicsFunctionalError: except EbicsFunctionalError:
e = exc_info() e = exc_info()
error = _("EBICS Functional Error:") error = _("EBICS Functional Error:")
error += '\n' error += "\n"
error += '%s (code: %s)' % (e[1].message, e[1].code) error += "{} (code: {})".format(e[1].message, e[1].code)
raise UserError(error) raise UserError(error)
except Exception: except Exception:
exctype, value = exc_info()[:2] exctype, value = exc_info()[:2]
error = _("EBICS Initialisation Error:") error = _("EBICS Initialisation Error:")
error += '\n' + str(exctype) + '\n' + str(value) error += "\n" + str(exctype) + "\n" + str(value)
raise UserError(error) raise UserError(error)
public_bank_keys = public_bank_keys.encode() public_bank_keys = public_bank_keys.encode()
tmp_dir = os.path.normpath(self.ebics_config_id.ebics_files + '/tmp') tmp_dir = os.path.normpath(self.ebics_config_id.ebics_files + "/tmp")
if not os.path.isdir(tmp_dir): if not os.path.isdir(tmp_dir):
os.makedirs(tmp_dir, mode=0o700) os.makedirs(tmp_dir, mode=0o700)
fn_date = fields.Date.today().isoformat() fn_date = fields.Date.today().isoformat()
fn = '_'.join( fn = (
[self.ebics_config_id.ebics_host, 'public_bank_keys', fn_date] "_".join([self.ebics_config_id.ebics_host, "public_bank_keys", fn_date])
) + '.txt' + ".txt"
self.write({ )
'ebics_public_bank_keys': base64.encodestring(public_bank_keys), self.write(
'ebics_public_bank_keys_fn': fn, {
'state': 'to_verify', "ebics_public_bank_keys": base64.encodestring(public_bank_keys),
}) "ebics_public_bank_keys_fn": fn,
"state": "to_verify",
}
)
return True return True
@ -435,32 +457,32 @@ class EbicsUserID(models.Model):
and activate the bank keyu. and activate the bank keyu.
""" """
self.ensure_one() self.ensure_one()
if self.state != 'to_verify': if self.state != "to_verify":
raise UserError( raise UserError(_("Set state to 'Verification'."))
_("Set state to 'Verification'."))
keyring = EbicsKeyRing( keyring = EbicsKeyRing(
keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase) keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase
)
bank = EbicsBank( bank = EbicsBank(
keyring=keyring, keyring=keyring,
hostid=self.ebics_config_id.ebics_host, hostid=self.ebics_config_id.ebics_host,
url=self.ebics_config_id.ebics_url) url=self.ebics_config_id.ebics_url,
)
bank.activate_keys() bank.activate_keys()
return self.write({'state': 'active_keys'}) return self.write({"state": "active_keys"})
def change_passphrase(self): def change_passphrase(self):
self.ensure_one() self.ensure_one()
ctx = dict(self._context, default_ebics_userid_id=self.id) ctx = dict(self._context, default_ebics_userid_id=self.id)
module = __name__.split('addons.')[1].split('.')[0] module = __name__.split("addons.")[1].split(".")[0]
view = self.env.ref( view = self.env.ref("%s.ebics_change_passphrase_view_form" % module)
'%s.ebics_change_passphrase_view_form' % module)
return { return {
'name': _('EBICS keys change passphrase'), "name": _("EBICS keys change passphrase"),
'view_type': 'form', "view_type": "form",
'view_mode': 'form', "view_mode": "form",
'res_model': 'ebics.change.passphrase', "res_model": "ebics.change.passphrase",
'view_id': view.id, "view_id": view.id,
'target': 'new', "target": "new",
'context': ctx, "context": ctx,
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
} }

View File

@ -13,24 +13,25 @@ try:
import fintech import fintech
except ImportError: except ImportError:
fintech = None fintech = None
_logger.warning('Failed to import fintech') _logger.warning("Failed to import fintech")
fintech_register_name = config.get('fintech_register_name') fintech_register_name = config.get("fintech_register_name")
fintech_register_keycode = config.get('fintech_register_keycode') fintech_register_keycode = config.get("fintech_register_keycode")
fintech_register_users = config.get('fintech_register_users') fintech_register_users = config.get("fintech_register_users")
try: try:
if fintech: if fintech:
fintech_register_users = ( fintech_register_users = (
fintech_register_users fintech_register_users
and [x.strip() for x in fintech_register_users.split(',')] and [x.strip() for x in fintech_register_users.split(",")]
or None or None
) )
fintech.cryptolib = 'cryptography' fintech.cryptolib = "cryptography"
fintech.register( fintech.register(
name=fintech_register_name, name=fintech_register_name,
keycode=fintech_register_keycode, keycode=fintech_register_keycode,
users=fintech_register_users) users=fintech_register_users,
)
except RuntimeError as e: except RuntimeError as e:
if str(e) == "'register' can be called only once": if str(e) == "'register' can be called only once":
pass pass
@ -39,7 +40,7 @@ except RuntimeError as e:
fintech.register() fintech.register()
except Exception: except Exception:
msg = "fintech.register error" msg = "fintech.register error"
tb = ''.join(format_exception(*exc_info())) tb = "".join(format_exception(*exc_info()))
msg += '\n%s' % tb msg += "\n%s" % tb
_logger.error(msg) _logger.error(msg)
fintech.register() fintech.register()

View File

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

View File

@ -6,10 +6,10 @@
<field name="model">ebics.config</field> <field name="model">ebics.config</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="EBICS Configuration" decoration-muted="state == 'draft'"> <tree string="EBICS Configuration" decoration-muted="state == 'draft'">
<field name="name"/> <field name="name" />
<field name="ebics_host"/> <field name="ebics_host" />
<field name="state"/> <field name="state" />
<field name="active"/> <field name="active" />
</tree> </tree>
</field> </field>
</record> </record>
@ -20,44 +20,63 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="EBICS Configuration"> <form string="EBICS Configuration">
<header> <header>
<button name="set_to_draft" states="confirm" string="Set to Draft" type="object" <button
groups="account_ebics.group_ebics_manager" name="set_to_draft"
help="Set to Draft in order to change the EBICS configuration parameters."/> states="confirm"
<button name="set_to_confirm" states="draft" string="Confirm" type="object" class="oe_highlight" string="Set to Draft"
groups="account_ebics.group_ebics_manager" type="object"
help="The EBICS configuration must be confirmed before it can used for bank transactions."/> groups="account_ebics.group_ebics_manager"
<field name="state" widget="statusbar"/> help="Set to Draft in order to change the EBICS configuration parameters."
/>
<button
name="set_to_confirm"
states="draft"
string="Confirm"
type="object"
class="oe_highlight"
groups="account_ebics.group_ebics_manager"
help="The EBICS configuration must be confirmed before it can used for bank transactions."
/>
<field name="state" widget="statusbar" />
</header> </header>
<field name="active" invisible="1" /> <field name="active" invisible="1" />
<widget name="web_ribbon" <widget
text="Archived" name="web_ribbon"
bg_color="bg-danger" text="Archived"
attrs="{'invisible': [('active', '=', True)]}"/> bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
<group name="main"> <group name="main">
<group name="main-left"> <group name="main-left">
<field name="name" colspan="2"/> <field name="name" colspan="2" />
<field name="ebics_host"/> <field name="ebics_host" />
<field name="ebics_url"/> <field name="ebics_url" />
<field name="ebics_partner"/> <field name="ebics_partner" />
<field name="ebics_files"/> <field name="ebics_files" />
<field name="ebics_keys"/> <field name="ebics_keys" />
</group> </group>
<group name="main-right"> <group name="main-right">
<field name="journal_ids" widget="many2many_tags" options="{'no_create': True}"/> <field
<field name="ebics_version"/> name="journal_ids"
<field name="ebics_key_version"/> widget="many2many_tags"
<field name="ebics_key_bitlength"/> options="{'no_create': True}"
<field name="order_number" />
attrs="{'invisible': [('ebics_version', '=', 'H004')]}"/> <field name="ebics_version" />
<field name="ebics_key_version" />
<field name="ebics_key_bitlength" />
<field
name="order_number"
attrs="{'invisible': [('ebics_version', '=', 'H004')]}"
/>
</group> </group>
<field name="company_ids" invisible="1"/> <field name="company_ids" invisible="1" />
</group> </group>
<notebook> <notebook>
<page string="EBICS Users" groups="account_ebics.group_ebics_manager"> <page string="EBICS Users" groups="account_ebics.group_ebics_manager">
<field name="ebics_userid_ids"/> <field name="ebics_userid_ids" />
</page> </page>
<page string="File Formats" groups="account_ebics.group_ebics_manager"> <page string="File Formats" groups="account_ebics.group_ebics_manager">
<field name="ebics_file_format_ids"/> <field name="ebics_file_format_ids" />
</page> </page>
</notebook> </notebook>
</form> </form>

View File

@ -6,11 +6,11 @@
<field name="model">ebics.file.format</field> <field name="model">ebics.file.format</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="EBICS File Formats"> <tree string="EBICS File Formats">
<field name="type"/> <field name="type" />
<field name="order_type"/> <field name="order_type" />
<field name="signature_class"/> <field name="signature_class" />
<field name="name"/> <field name="name" />
<field name="description"/> <field name="description" />
</tree> </tree>
</field> </field>
</record> </record>
@ -22,20 +22,22 @@
<form string="EBICS File Format"> <form string="EBICS File Format">
<group name="main"> <group name="main">
<group name="main-left"> <group name="main-left">
<field name="type"/> <field name="type" />
<field name="suffix"/> <field name="suffix" />
<field name="download_process_method" <field
attrs="{'invisible': [('type', '=', 'up')]}" name="download_process_method"
force_save="1"/> attrs="{'invisible': [('type', '=', 'up')]}"
<field name="signature_class"/> force_save="1"
/>
<field name="signature_class" />
</group> </group>
<group name="main-right"> <group name="main-right">
<field name="order_type"/> <field name="order_type" />
<field name="name"/> <field name="name" />
</group> </group>
</group> </group>
<group name="description"> <group name="description">
<field name="description"/> <field name="description" />
</group> </group>
</form> </form>
</field> </field>

View File

@ -7,18 +7,26 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Search EBICS Files"> <search string="Search EBICS Files">
<group col="10" colspan="4"> <group col="10" colspan="4">
<field name="date_from"/> <field name="date_from" />
<field name="date_to"/> <field name="date_to" />
<field name="name"/> <field name="name" />
<field name="format_id"/> <field name="format_id" />
<field name="user_id"/> <field name="user_id" />
<field name="company_ids" widget="selection" groups="base.group_multi_company"/> <field
name="company_ids"
widget="selection"
groups="base.group_multi_company"
/>
</group> </group>
<newline/> <newline />
<group expand="0" string="Group By"> <group expand="0" string="Group By">
<filter string="File Format" name="file_format" context="{'group_by':'format_id'}"/> <filter
<filter string="State" name="state" context="{'group_by':'state'}"/> string="File Format"
<filter string="User" name="user" context="{'group_by':'user_id'}"/> name="file_format"
context="{'group_by':'format_id'}"
/>
<filter string="State" name="state" context="{'group_by':'state'}" />
<filter string="User" name="user" context="{'group_by':'user_id'}" />
</group> </group>
</search> </search>
</field> </field>
@ -31,14 +39,18 @@
<field name="model">ebics.file</field> <field name="model">ebics.file</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="EBICS Files" decoration-muted="state=='draft'" create="false"> <tree string="EBICS Files" decoration-muted="state=='draft'" create="false">
<field name="date" string="Download Date"/> <field name="date" string="Download Date" />
<field name="name"/> <field name="name" />
<field name="date_from"/> <field name="date_from" />
<field name="date_to"/> <field name="date_to" />
<field name="user_id"/> <field name="user_id" />
<field name="state"/> <field name="state" />
<field name="format_id"/> <field name="format_id" />
<field name="company_ids" widget="many2many_tags" groups="base.group_multi_company"/> <field
name="company_ids"
widget="many2many_tags"
groups="base.group_multi_company"
/>
</tree> </tree>
</field> </field>
</record> </record>
@ -50,34 +62,55 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="EBICS File" create="false"> <form string="EBICS File" create="false">
<header> <header>
<button name="set_to_draft" states="done" string="Set to Draft" type="object" groups="account.group_account_manager"/> <button
<button name="process" name="set_to_draft"
class="oe_highlight" states="done"
states="draft" string="Set to Draft"
string="Process" type="object"
type="object" groups="account.group_account_manager"
groups="account.group_account_invoice" />
help="Process the EBICS File"/> <button
<button name="set_to_done" states="draft" string="Set to Done" type="object" groups="account.group_account_manager"/> name="process"
<field name="state" widget="statusbar"/> class="oe_highlight"
states="draft"
string="Process"
type="object"
groups="account.group_account_invoice"
help="Process the EBICS File"
/>
<button
name="set_to_done"
states="draft"
string="Set to Done"
type="object"
groups="account.group_account_manager"
/>
<field name="state" widget="statusbar" />
</header> </header>
<group colspan="4" col="4"> <group colspan="4" col="4">
<field name="date" string="Download Date"/> <field name="date" string="Download Date" />
<field name="name"/> <field name="name" />
<field name="data" filename="name"/> <field name="data" filename="name" />
<field name="format_id"/> <field name="format_id" />
<field name="date_from"/> <field name="date_from" />
<field name="date_to"/> <field name="date_to" />
<field name="user_id"/> <field name="user_id" />
<field name="ebics_userid_id"/> <field name="ebics_userid_id" />
<field name="company_ids" widget="many2many_tags" groups="base.group_multi_company"/> <field
name="company_ids"
widget="many2many_tags"
groups="base.group_multi_company"
/>
</group> </group>
<notebook> <notebook>
<page string="Additional Information"> <page string="Additional Information">
<field name="note" nolabel="1"/> <field name="note" nolabel="1" />
</page> </page>
<page string="Bank Statements" attrs="{'invisible':[('bank_statement_ids','=',[])]}"> <page
<field name="bank_statement_ids" nolabel="1"/> string="Bank Statements"
attrs="{'invisible':[('bank_statement_ids','=',[])]}"
>
<field name="bank_statement_ids" nolabel="1" />
</page> </page>
</notebook> </notebook>
</form> </form>
@ -91,12 +124,16 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Process EBICS File"> <form string="Process EBICS File">
<separator colspan="4" string="Results :" /> <separator colspan="4" string="Results :" />
<field name="note_process" colspan="4" nolabel="1" width="850" height="400"/> <field name="note_process" colspan="4" nolabel="1" width="850" height="400" />
<footer> <footer>
<button name="action_open_bank_statements" string="View Bank Statement(s)" <button
type="object" class="oe_highlight" name="action_open_bank_statements"
invisible="not context.get('statement_ids')"/> string="View Bank Statement(s)"
<button name="button_close" type="object" string="Close"/> type="object"
class="oe_highlight"
invisible="not context.get('statement_ids')"
/>
<button name="button_close" type="object" string="Close" />
</footer> </footer>
</form> </form>
</field> </field>
@ -107,23 +144,23 @@
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="res_model">ebics.file</field> <field name="res_model">ebics.file</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="view_id" eval="False"/> <field name="view_id" eval="False" />
<field name="domain">[('type','=','down')]</field> <field name="domain">[('type','=','down')]</field>
<field name="search_view_id" ref="ebics_file_view_search"/> <field name="search_view_id" ref="ebics_file_view_search" />
</record> </record>
<record id="ebics_file_action_download_tree" model="ir.actions.act_window.view"> <record id="ebics_file_action_download_tree" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/> <field eval="1" name="sequence" />
<field name="view_mode">tree</field> <field name="view_mode">tree</field>
<field name="view_id" ref="ebics_file_view_tree_download"/> <field name="view_id" ref="ebics_file_view_tree_download" />
<field name="act_window_id" ref="ebics_file_action_download"/> <field name="act_window_id" ref="ebics_file_action_download" />
</record> </record>
<record id="ebics_file_action_download_form" model="ir.actions.act_window.view"> <record id="ebics_file_action_download_form" model="ir.actions.act_window.view">
<field eval="2" name="sequence"/> <field eval="2" name="sequence" />
<field name="view_mode">form</field> <field name="view_mode">form</field>
<field name="view_id" ref="ebics_file_view_form_download"/> <field name="view_id" ref="ebics_file_view_form_download" />
<field name="act_window_id" ref="ebics_file_action_download"/> <field name="act_window_id" ref="ebics_file_action_download" />
</record> </record>
<!-- Upload --> <!-- Upload -->
@ -133,12 +170,16 @@
<field name="model">ebics.file</field> <field name="model">ebics.file</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="EBICS Files" decoration-muted="state=='draft'" create="false"> <tree string="EBICS Files" decoration-muted="state=='draft'" create="false">
<field name="date" string="Upload Date"/> <field name="date" string="Upload Date" />
<field name="name"/> <field name="name" />
<field name="user_id"/> <field name="user_id" />
<field name="state"/> <field name="state" />
<field name="format_id"/> <field name="format_id" />
<field name="company_ids" widget="many2many_tags" groups="base.group_multi_company"/> <field
name="company_ids"
widget="many2many_tags"
groups="base.group_multi_company"
/>
</tree> </tree>
</field> </field>
</record> </record>
@ -150,22 +191,38 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="EBICS File" create="false"> <form string="EBICS File" create="false">
<header> <header>
<button name="set_to_draft" states="done" string="Set to Draft" type="object" groups="account.group_account_manager"/> <button
<button name="set_to_done" states="draft" string="Set to Done" type="object" groups="account.group_account_manager"/> name="set_to_draft"
<field name="state" widget="statusbar"/> 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> </header>
<group colspan="4" col="4"> <group colspan="4" col="4">
<field name="date" string="Upload Date"/> <field name="date" string="Upload Date" />
<field name="name"/> <field name="name" />
<field name="data" filename="name"/> <field name="data" filename="name" />
<field name="format_id"/> <field name="format_id" />
<field name="user_id"/> <field name="user_id" />
<field name="ebics_userid_id"/> <field name="ebics_userid_id" />
<field name="company_ids" widget="many2many_tags" groups="base.group_multi_company"/> <field
name="company_ids"
widget="many2many_tags"
groups="base.group_multi_company"
/>
</group> </group>
<notebook> <notebook>
<page string="Additional Information"> <page string="Additional Information">
<field name="note" nolabel="1"/> <field name="note" nolabel="1" />
</page> </page>
</notebook> </notebook>
</form> </form>
@ -177,23 +234,23 @@
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="res_model">ebics.file</field> <field name="res_model">ebics.file</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="view_id" eval="False"/> <field name="view_id" eval="False" />
<field name="domain">[('type','=','up')]</field> <field name="domain">[('type','=','up')]</field>
<field name="search_view_id" ref="ebics_file_view_search"/> <field name="search_view_id" ref="ebics_file_view_search" />
</record> </record>
<record id="ebics_file_action_upload_tree" model="ir.actions.act_window.view"> <record id="ebics_file_action_upload_tree" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/> <field eval="1" name="sequence" />
<field name="view_mode">tree</field> <field name="view_mode">tree</field>
<field name="view_id" ref="ebics_file_view_tree_upload"/> <field name="view_id" ref="ebics_file_view_tree_upload" />
<field name="act_window_id" ref="ebics_file_action_upload"/> <field name="act_window_id" ref="ebics_file_action_upload" />
</record> </record>
<record id="ebics_file_action_upload_form" model="ir.actions.act_window.view"> <record id="ebics_file_action_upload_form" model="ir.actions.act_window.view">
<field eval="2" name="sequence"/> <field eval="2" name="sequence" />
<field name="view_mode">form</field> <field name="view_mode">form</field>
<field name="view_id" ref="ebics_file_view_form_upload"/> <field name="view_id" ref="ebics_file_view_form_upload" />
<field name="act_window_id" ref="ebics_file_action_upload"/> <field name="act_window_id" ref="ebics_file_action_upload" />
</record> </record>
</odoo> </odoo>

View File

@ -6,10 +6,10 @@
<field name="model">ebics.userid</field> <field name="model">ebics.userid</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="EBICS UserID" decoration-muted="state != 'active_keys'"> <tree string="EBICS UserID" decoration-muted="state != 'active_keys'">
<field name="name"/> <field name="name" />
<field name="signature_class"/> <field name="signature_class" />
<field name="state"/> <field name="state" />
<field name="active"/> <field name="active" />
</tree> </tree>
</field> </field>
</record> </record>
@ -20,67 +20,136 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="EBICS UserID"> <form string="EBICS UserID">
<header groups="account_ebics.group_ebics_manager"> <header groups="account_ebics.group_ebics_manager">
<button name="ebics_init_1" states="draft" string="EBICS Initialisation" type="object" class="oe_highlight" <button
help="Initialise EBICS Bank Keys"/> name="ebics_init_1"
<button name="ebics_init_2" states="init" string="Account activated" type="object" class="oe_highlight" states="draft"
help="EBICS Initialisation - Push this button when the account has been activated by the bank."/> string="EBICS Initialisation"
<button name="ebics_init_3" states="get_bank_keys" string="Get Bank Keys" type="object" class="oe_highlight" type="object"
help="EBICS Initialisation - After the account has been activated the public bank keys must be downloaded and checked for consistency."/> class="oe_highlight"
<button name="ebics_init_4" states="to_verify" string="Bank Keys Verified" type="object" class="oe_highlight" help="Initialise EBICS Bank Keys"
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" <button
attrs="{'invisible': [('ebics_keys_found', '=', False)]}"/> name="ebics_init_2"
<button name="set_to_draft" states="active_keys" string="Set to Draft" type="object" states="init"
help="Set to Draft in order to reinitialize your bank connection."/> string="Account activated"
<button name="set_to_get_bank_keys" states="active_keys" string="Renew Bank Keys" type="object" type="object"
help="Use this button to update the EBICS certificates of your bank."/> class="oe_highlight"
<button name="set_to_active_keys" states="draft" string="Force Active Keys" type="object" help="EBICS Initialisation - Push this button when the account has been activated by the bank."
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"/> <button
name="ebics_init_3"
states="get_bank_keys"
string="Get Bank Keys"
type="object"
class="oe_highlight"
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"
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)]}"
/>
<button
name="set_to_draft"
states="active_keys"
string="Set to Draft"
type="object"
help="Set to Draft in order to reinitialize your bank connection."
/>
<button
name="set_to_get_bank_keys"
states="active_keys"
string="Renew Bank Keys"
type="object"
help="Use this button to update the EBICS certificates of your bank."
/>
<button
name="set_to_active_keys"
states="draft"
string="Force Active Keys"
type="object"
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> </header>
<group name="main" attrs="{'readonly': [('state', '!=', 'draft')]}"> <group name="main" attrs="{'readonly': [('state', '!=', 'draft')]}">
<field name="ebics_keys_found" invisible="1"/> <field name="ebics_keys_found" invisible="1" />
<field name="ebics_keys_fn" invisible="1"/> <field name="ebics_keys_fn" invisible="1" />
<group name="main-left"> <group name="main-left">
<field name="name"/> <field name="name" />
<field name="ebics_passphrase" password="True" <field
attrs="{'required': [('state', '=', 'draft')]}"/> name="ebics_passphrase"
<field name="swift_3skey" password="True"
attrs="{'invisible': [('signature_class', '=', 'T')]}"/> attrs="{'required': [('state', '=', 'draft')]}"
<field name="swift_3skey_certificate_fn" invisible="1"/> />
<field name="swift_3skey_certificate" filename="swift_3skey_certificate_fn" <field
attrs="{'invisible': [('swift_3skey', '=', False)], 'required': [('swift_3skey', '=', True)]}"/> name="swift_3skey"
<field name="active"/> attrs="{'invisible': [('signature_class', '=', 'T')]}"
/>
<field name="swift_3skey_certificate_fn" invisible="1" />
<field
name="swift_3skey_certificate"
filename="swift_3skey_certificate_fn"
attrs="{'invisible': [('swift_3skey', '=', False)], 'required': [('swift_3skey', '=', True)]}"
/>
<field name="active" />
</group> </group>
<group name="main-right"> <group name="main-right">
<field name="signature_class"/> <field name="signature_class" />
<field name="user_ids" widget="many2many_tags" options="{'no_create': True}"/> <field
<field name="ebics_key_x509"/> name="user_ids"
widget="many2many_tags"
options="{'no_create': True}"
/>
<field name="ebics_key_x509" />
</group> </group>
</group> </group>
<group col="4" name="dn" attrs="{'invisible': [('ebics_key_x509', '=', False)], 'readonly': [('state', '!=', 'draft')]}"> <group
col="4"
name="dn"
attrs="{'invisible': [('ebics_key_x509', '=', False)], 'readonly': [('state', '!=', 'draft')]}"
>
<group colspan="4" col="1"> <group colspan="4" col="1">
<strong>Distinguished Name attributes used to create self-signed X.509 certificates:</strong> <strong
>Distinguished Name attributes used to create self-signed X.509 certificates:</strong>
</group> </group>
<group name="dn_l" colspan="2"> <group name="dn_l" colspan="2">
<field name="ebics_key_x509_dn_cn"/> <field name="ebics_key_x509_dn_cn" />
<field name="ebics_key_x509_dn_o"/> <field name="ebics_key_x509_dn_o" />
<field name="ebics_key_x509_dn_l"/> <field name="ebics_key_x509_dn_l" />
<field name="ebics_key_x509_dn_c"/> <field name="ebics_key_x509_dn_c" />
</group> </group>
<group name="dn_r" colspan="2"> <group name="dn_r" colspan="2">
<field name="ebics_key_x509_dn_e"/> <field name="ebics_key_x509_dn_e" />
<field name="ebics_key_x509_dn_ou"/> <field name="ebics_key_x509_dn_ou" />
<field name="ebics_key_x509_dn_st"/> <field name="ebics_key_x509_dn_st" />
</group> </group>
</group> </group>
<group colspan="4" name="ebics_ini_letter" attrs="{'invisible': [('ebics_ini_letter', '=', False)]}"> <group
<field name="ebics_ini_letter_fn" invisible="1"/> colspan="4"
<field name="ebics_ini_letter" filename="ebics_ini_letter_fn"/> 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>
<group colspan="4" name="ebics_public_bank_keys" attrs="{'invisible': [('ebics_public_bank_keys', '=', False)]}"> <group
<field name="ebics_public_bank_keys_fn" invisible="1"/> colspan="4"
<field name="ebics_public_bank_keys" filename="ebics_public_bank_keys_fn"/> 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>
</form> </form>
</field> </field>

View File

@ -1,58 +1,76 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<odoo> <odoo>
<menuitem id="ebics_processing_menu" <menuitem
name="EBICS Processing" id="ebics_processing_menu"
parent="account.menu_finance" name="EBICS Processing"
sequence="4"/> parent="account.menu_finance"
sequence="4"
/>
<menuitem id="ebics_xfer_menu_download" <menuitem
name="EBICS Download" id="ebics_xfer_menu_download"
parent="ebics_processing_menu" name="EBICS Download"
action="ebics_xfer_action_download" parent="ebics_processing_menu"
sequence="10"/> action="ebics_xfer_action_download"
sequence="10"
/>
<menuitem id="ebics_xfer_menu_upload" <menuitem
name="EBICS Upload" id="ebics_xfer_menu_upload"
parent="ebics_processing_menu" name="EBICS Upload"
action="ebics_xfer_action_upload" parent="ebics_processing_menu"
sequence="20"/> action="ebics_xfer_action_upload"
sequence="20"
/>
<menuitem id="ebics_file_menu" <menuitem
name="EBICS Files" id="ebics_file_menu"
parent="ebics_processing_menu" name="EBICS Files"
sequence="30"/> parent="ebics_processing_menu"
sequence="30"
/>
<menuitem id="ebics_file_menu_download" <menuitem
name="Download" id="ebics_file_menu_download"
parent="ebics_file_menu" name="Download"
action="ebics_file_action_download" parent="ebics_file_menu"
sequence="10"/> action="ebics_file_action_download"
sequence="10"
/>
<menuitem id="ebics_file_menu_upload" <menuitem
name="Upload" id="ebics_file_menu_upload"
parent="ebics_file_menu" name="Upload"
action="ebics_file_action_upload" parent="ebics_file_menu"
sequence="20"/> action="ebics_file_action_upload"
sequence="20"
/>
<menuitem id="ebics_menu" <menuitem
name="EBICS" id="ebics_menu"
parent='account.menu_finance_configuration' name="EBICS"
groups="account_ebics.group_ebics_manager" parent='account.menu_finance_configuration'
sequence="100"/> groups="account_ebics.group_ebics_manager"
sequence="100"
/>
<menuitem id="ebics_config_menu" <menuitem
name="EBICS Configuration" id="ebics_config_menu"
parent="ebics_menu" name="EBICS Configuration"
action="ebics_config_action" parent="ebics_menu"
groups="account_ebics.group_ebics_manager" action="ebics_config_action"
sequence="10"/> groups="account_ebics.group_ebics_manager"
sequence="10"
/>
<menuitem id="ebics_file_format_menu" <menuitem
name="EBICS File Formats" id="ebics_file_format_menu"
parent="ebics_menu" name="EBICS File Formats"
action="ebics_file_format_action" parent="ebics_menu"
groups="account_ebics.group_ebics_manager" action="ebics_file_format_action"
sequence="20"/> groups="account_ebics.group_ebics_manager"
sequence="20"
/>
</odoo> </odoo>

View File

@ -11,65 +11,58 @@ _logger = logging.getLogger(__name__)
try: try:
import fintech import fintech
from fintech.ebics import EbicsKeyRing from fintech.ebics import EbicsKeyRing
fintech.cryptolib = 'cryptography'
fintech.cryptolib = "cryptography"
except ImportError: except ImportError:
_logger.warning('Failed to import fintech') _logger.warning("Failed to import fintech")
class EbicsChangePassphrase(models.TransientModel): class EbicsChangePassphrase(models.TransientModel):
_name = 'ebics.change.passphrase' _name = "ebics.change.passphrase"
_description = 'Change EBICS keys passphrase' _description = "Change EBICS keys passphrase"
ebics_userid_id = fields.Many2one( ebics_userid_id = fields.Many2one(
comodel_name='ebics.userid', comodel_name="ebics.userid", string="EBICS UserID", readonly=True
string='EBICS UserID', )
readonly=True) old_pass = fields.Char(string="Old Passphrase", required=True)
old_pass = fields.Char( new_pass = fields.Char(string="New Passphrase", required=True)
string='Old Passphrase', new_pass_check = fields.Char(string="New Passphrase (verification)", required=True)
required=True) note = fields.Text(string="Notes", readonly=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)
def change_passphrase(self): def change_passphrase(self):
self.ensure_one() self.ensure_one()
if self.old_pass != self.ebics_userid_id.ebics_passphrase: if self.old_pass != self.ebics_userid_id.ebics_passphrase:
raise UserError(_( raise UserError(_("Incorrect old passphrase."))
"Incorrect old passphrase."))
if self.new_pass != self.new_pass_check: if self.new_pass != self.new_pass_check:
raise UserError(_( raise UserError(_("New passphrase verification error."))
"New passphrase verification error."))
if self.new_pass == self.ebics_userid_id.ebics_passphrase: if self.new_pass == self.ebics_userid_id.ebics_passphrase:
raise UserError(_( raise UserError(_("New passphrase equal to old passphrase."))
"New passphrase equal to old passphrase."))
try: try:
keyring = EbicsKeyRing( keyring = EbicsKeyRing(
keys=self.ebics_userid_id.ebics_keys_fn, keys=self.ebics_userid_id.ebics_keys_fn,
passphrase=self.ebics_userid_id.ebics_passphrase) passphrase=self.ebics_userid_id.ebics_passphrase,
)
keyring.change_passphrase(self.new_pass) keyring.change_passphrase(self.new_pass)
except ValueError as e: except ValueError as e:
raise UserError(str(e)) raise UserError(str(e))
self.ebics_userid.ebics_passphrase = self.new_pass self.ebics_userid.ebics_passphrase = self.new_pass
self.note = "The EBICS Passphrase has been changed." self.note = "The EBICS Passphrase has been changed."
module = __name__.split('addons.')[1].split('.')[0] module = __name__.split("addons.")[1].split(".")[0]
result_view = self.env.ref( result_view = self.env.ref(
'%s.ebics_change_passphrase_view_form_result' % module) "%s.ebics_change_passphrase_view_form_result" % module
)
return { return {
'name': _('EBICS Keys Change Passphrase'), "name": _("EBICS Keys Change Passphrase"),
'res_id': self.id, "res_id": self.id,
'view_type': 'form', "view_type": "form",
'view_mode': 'form', "view_mode": "form",
'res_model': 'ebics.change.passphrase', "res_model": "ebics.change.passphrase",
'view_id': result_view.id, "view_id": result_view.id,
'target': 'new', "target": "new",
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
} }
def button_close(self): def button_close(self):
self.ensure_one() self.ensure_one()
return {'type': 'ir.actions.act_window_close'} return {"type": "ir.actions.act_window_close"}

View File

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

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="ebics_xfer_view_form_download" model="ir.ui.view"> <record id="ebics_xfer_view_form_download" model="ir.ui.view">
@ -8,23 +8,36 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="EBICS File Download"> <form string="EBICS File Download">
<group> <group>
<separator string="Select your bank :" colspan="2"/> <separator string="Select your bank :" colspan="2" />
<field name="ebics_config_id" required="1" options="{'no_create': True, 'no_open': True}"/> <field
<field name="ebics_userid_id" name="ebics_config_id"
domain="[('ebics_config_id', '=', ebics_config_id)]" required="1"
required="1" options="{'no_create': True, 'no_open': True}"
options="{'no_create': True, 'no_open': True}"/> />
<field name="date_from"/> <field
<field name="date_to"/> name="ebics_userid_id"
<field name="format_id" domain="[('ebics_config_id', '=', ebics_config_id)]"
domain="[('type', '=', 'down'), ('id', 'in', allowed_format_ids)]"/> required="1"
<field name="order_type"/> options="{'no_create': True, 'no_open': True}"
<field name="allowed_format_ids" invisible="1"/> />
<field name="date_from" />
<field name="date_to" />
<field
name="format_id"
domain="[('type', '=', 'down'), ('id', 'in', allowed_format_ids)]"
/>
<field name="order_type" />
<field name="allowed_format_ids" invisible="1" />
</group> </group>
<footer> <footer>
<button name="ebics_download" string="Download Files" type="object" class="oe_highlight"/> <button
name="ebics_download"
string="Download Files"
type="object"
class="oe_highlight"
/>
or or
<button string="Cancel" class="oe_link" special="cancel"/> <button string="Cancel" class="oe_link" special="cancel" />
</footer> </footer>
</form> </form>
</field> </field>
@ -37,26 +50,43 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="EBICS File Upload"> <form string="EBICS File Upload">
<group> <group>
<separator string="Select your bank :" colspan="2"/> <separator string="Select your bank :" colspan="2" />
<field name="ebics_config_id" required="1" options="{'no_create': True, 'no_open': True}"/> <field
<field name="ebics_userid_id" name="ebics_config_id"
domain="[('ebics_config_id', '=', ebics_config_id)]" required="1"
required="1" options="{'no_create': True, 'no_open': True}"
options="{'no_create': True, 'no_open': True}"/> />
<separator string="Select your file :" colspan="2"/> <field
<field name="upload_data" filename="upload_fname" required="1"/> name="ebics_userid_id"
<field name="upload_fname" invisible="1"/> domain="[('ebics_config_id', '=', ebics_config_id)]"
<field name="upload_fname_dummy"/> required="1"
<field name="format_id" required="1" options="{'no_create': True, 'no_open': True}"
domain="[('type', '=', 'up'), ('id', 'in', allowed_format_ids)]"/> />
<field name="order_type"/> <separator string="Select your file :" colspan="2" />
<field name="test_mode" attrs="{'invisible': [('order_type', '!=', 'FUL')]}"/> <field name="upload_data" filename="upload_fname" required="1" />
<field name="allowed_format_ids" invisible="1"/> <field name="upload_fname" invisible="1" />
<field name="upload_fname_dummy" />
<field
name="format_id"
required="1"
domain="[('type', '=', 'up'), ('id', 'in', allowed_format_ids)]"
/>
<field name="order_type" />
<field
name="test_mode"
attrs="{'invisible': [('order_type', '!=', 'FUL')]}"
/>
<field name="allowed_format_ids" invisible="1" />
</group> </group>
<footer> <footer>
<button name="ebics_upload" string="Upload File" type="object" class="oe_highlight"/> <button
name="ebics_upload"
string="Upload File"
type="object"
class="oe_highlight"
/>
or or
<button string="Cancel" class="oe_link" special="cancel"/> <button string="Cancel" class="oe_link" special="cancel" />
</footer> </footer>
</form> </form>
</field> </field>
@ -69,11 +99,16 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="EBICS File Transfer"> <form string="EBICS File Transfer">
<separator colspan="4" string="Results :" /> <separator colspan="4" string="Results :" />
<field name="note" colspan="4" nolabel="1" width="850" height="400"/> <field name="note" colspan="4" nolabel="1" width="850" height="400" />
<footer> <footer>
<button name="view_ebics_file" type="object" string="View EBICS File(s)" class="oe_highlight" <button
invisible="not context.get('ebics_file_ids')"/> name="view_ebics_file"
<button name="button_close" type="object" string="Close"/> 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> </footer>
</form> </form>
</field> </field>
@ -86,7 +121,7 @@
<field name="view_mode">form</field> <field name="view_mode">form</field>
<field name="target">new</field> <field name="target">new</field>
<field name="context">{'ebics_download': 1}</field> <field name="context">{'ebics_download': 1}</field>
<field name="view_id" ref="ebics_xfer_view_form_download"/> <field name="view_id" ref="ebics_xfer_view_form_download" />
</record> </record>
<record id="ebics_xfer_action_upload" model="ir.actions.act_window"> <record id="ebics_xfer_action_upload" model="ir.actions.act_window">
@ -96,7 +131,7 @@
<field name="view_mode">form</field> <field name="view_mode">form</field>
<field name="target">new</field> <field name="target">new</field>
<field name="context">{'ebics_upload': 1}</field> <field name="context">{'ebics_upload': 1}</field>
<field name="view_id" ref="ebics_xfer_view_form_upload"/> <field name="view_id" ref="ebics_xfer_view_form_upload" />
</record> </record>
</odoo> </odoo>

View File

@ -2,16 +2,16 @@
# License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl).
{ {
'name': 'account_ebics with OCA Bank Statement Imoort', "name": "account_ebics with OCA Bank Statement Imoort",
'summary': "Use OCA Bank Statement Import with account_ebics", "summary": "Use OCA Bank Statement Import with account_ebics",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'author': 'Noviat', "author": "Noviat",
'category': 'Hidden', "category": "Hidden",
'license': 'LGPL-3', "license": "LGPL-3",
'depends': [ "depends": [
'account_ebics', "account_ebics",
'account_statement_import', "account_statement_import",
], ],
'installable': True, "installable": True,
'auto_install': True, "auto_install": True,
} }

View File

@ -3,32 +3,34 @@
import logging import logging
from odoo import models, _ from odoo import _, models
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class AccountStatementImport(models.TransientModel): class AccountStatementImport(models.TransientModel):
_inherit = 'account.statement.import' _inherit = "account.statement.import"
def _check_parsed_data(self, stmts_vals): def _check_parsed_data(self, stmts_vals):
""" Basic and structural verifications """ """ Basic and structural verifications """
if self.env.context.get('active_model') == 'ebics.file': if self.env.context.get("active_model") == "ebics.file":
message = False message = False
if len(stmts_vals) == 0: if len(stmts_vals) == 0:
message = _("This file doesn't contain any statement.") message = _("This file doesn't contain any statement.")
if not message: if not message:
no_st_line = True no_st_line = True
for vals in stmts_vals: for vals in stmts_vals:
if vals['transactions'] and len(vals['transactions']) > 0: if vals["transactions"] and len(vals["transactions"]) > 0:
no_st_line = False no_st_line = False
break break
if no_st_line: if no_st_line:
message = _('This file doesn\'t contain any transaction.') message = _("This file doesn't contain any transaction.")
if message: if message:
log_msg = _( log_msg = (
"Error detected while processing and EBICS File" _("Error detected while processing and EBICS File")
) + ':\n' + message + ":\n"
+ message
)
_logger.warn(log_msg) _logger.warn(log_msg)
return return
return super()._check_parsed_data(stmts_vals) return super()._check_parsed_data(stmts_vals)
@ -43,20 +45,16 @@ class AccountStatementImport(models.TransientModel):
We could also create empty bank statement (in state done) to clearly We could also create empty bank statement (in state done) to clearly
show days without transactions via the bank statement list view. show days without transactions via the bank statement list view.
""" """
if self.env.context.get('active_model') == 'ebics.file': if self.env.context.get("active_model") == "ebics.file":
transactions = False transactions = False
for st_vals in stmts_vals: for st_vals in stmts_vals:
if st_vals.get('transactions'): if st_vals.get("transactions"):
transactions = True transactions = True
break break
if not transactions: if not transactions:
message = _('This file doesn\'t contain any transaction.') message = _("This file doesn't contain any transaction.")
st_line_ids = [] st_line_ids = []
notifications = { notifications = {"type": "warning", "message": message, "details": ""}
'type': 'warning',
'message': message,
'details': ''
}
return st_line_ids, [notifications] return st_line_ids, [notifications]
return super()._create_bank_statements(stmts_vals, result) return super()._create_bank_statements(stmts_vals, result)

View File

@ -2,19 +2,17 @@
# License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl).
{ {
'name': 'account_ebics on Odoo Enterprise', "name": "account_ebics on Odoo Enterprise",
'summary': "Deploy account_ebics module on Odoo Enterprise", "summary": "Deploy account_ebics module on Odoo Enterprise",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'author': 'Noviat', "author": "Noviat",
'category': 'Hidden', "category": "Hidden",
'license': 'LGPL-3', "license": "LGPL-3",
'depends': [ "depends": [
'account_ebics', "account_ebics",
'account_accountant', "account_accountant",
], ],
'data': [ "data": ["views/account_ebics_menu.xml"],
'views/account_ebics_menu.xml' "installable": True,
], "auto_install": True,
'installable': True,
'auto_install': True,
} }

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="account_ebics.ebics_processing_menu" model="ir.ui.menu"> <record id="account_ebics.ebics_processing_menu" model="ir.ui.menu">
<field name="parent_id" eval="ref('account_accountant.menu_accounting')"/> <field name="parent_id" eval="ref('account_accountant.menu_accounting')" />
</record> </record>
</odoo> </odoo>

View File

@ -2,16 +2,16 @@
# License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl).
{ {
'name': 'account_ebics with Odoo Enterprise Bank Statement Import', "name": "account_ebics with Odoo Enterprise Bank Statement Import",
'summary': "Use Odoo Enterprise Bank Statement Import with account_ebics", "summary": "Use Odoo Enterprise Bank Statement Import with account_ebics",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'author': 'Noviat', "author": "Noviat",
'category': 'Hidden', "category": "Hidden",
'license': 'LGPL-3', "license": "LGPL-3",
'depends': [ "depends": [
'account_ebics_oe', "account_ebics_oe",
'account_bank_statement_import', "account_bank_statement_import",
], ],
'installable': True, "installable": True,
'auto_install': True, "auto_install": True,
} }

View File

@ -3,32 +3,34 @@
import logging import logging
from odoo import models, _ from odoo import _, models
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class AccountBankStatementImport(models.TransientModel): class AccountBankStatementImport(models.TransientModel):
_inherit = 'account.bank.statement.import' _inherit = "account.bank.statement.import"
def _check_parsed_data(self, stmts_vals, account_number): def _check_parsed_data(self, stmts_vals, account_number):
""" Basic and structural verifications """ """ Basic and structural verifications """
if self.env.context.get('active_model') == 'ebics.file': if self.env.context.get("active_model") == "ebics.file":
message = False message = False
if len(stmts_vals) == 0: if len(stmts_vals) == 0:
message = _("This file doesn't contain any statement.") message = _("This file doesn't contain any statement.")
if not message: if not message:
no_st_line = True no_st_line = True
for vals in stmts_vals: for vals in stmts_vals:
if vals['transactions'] and len(vals['transactions']) > 0: if vals["transactions"] and len(vals["transactions"]) > 0:
no_st_line = False no_st_line = False
break break
if no_st_line: if no_st_line:
message = _('This file doesn\'t contain any transaction.') message = _("This file doesn't contain any transaction.")
if message: if message:
log_msg = _( log_msg = (
"Error detected while processing and EBICS File" _("Error detected while processing and EBICS File")
) + ':\n' + message + ":\n"
+ message
)
_logger.warn(log_msg) _logger.warn(log_msg)
return return
super()._check_parsed_data(stmts_vals, account_number) super()._check_parsed_data(stmts_vals, account_number)
@ -43,20 +45,16 @@ class AccountBankStatementImport(models.TransientModel):
We could also create empty bank statement (in state done) to clearly We could also create empty bank statement (in state done) to clearly
show days without transactions via the bank statement list view. show days without transactions via the bank statement list view.
""" """
if self.env.context.get('active_model') == 'ebics.file': if self.env.context.get("active_model") == "ebics.file":
transactions = False transactions = False
for st_vals in stmts_vals: for st_vals in stmts_vals:
if st_vals.get('transactions'): if st_vals.get("transactions"):
transactions = True transactions = True
break break
if not transactions: if not transactions:
message = _('This file doesn\'t contain any transaction.') message = _("This file doesn't contain any transaction.")
st_line_ids = [] st_line_ids = []
notifications = { notifications = {"type": "warning", "message": message, "details": ""}
'type': 'warning',
'message': message,
'details': ''
}
return st_line_ids, [notifications] return st_line_ids, [notifications]
return super()._create_bank_statements(stmts_vals) return super()._create_bank_statements(stmts_vals)

View File

@ -2,16 +2,14 @@
# License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl).
{ {
'name': 'Upload Payment Order via EBICS', "name": "Upload Payment Order via EBICS",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'license': 'LGPL-3', "license": "LGPL-3",
'author': 'Noviat', "author": "Noviat",
'category': 'Accounting & Finance', "category": "Accounting & Finance",
'depends': [ "depends": ["account_ebics", "account_payment_order"],
'account_ebics', "data": [
'account_payment_order'], "views/account_payment_order.xml",
'data': [
'views/account_payment_order.xml',
], ],
'installable': True, "installable": True,
} }

View File

@ -6,57 +6,69 @@ from odoo.exceptions import UserError
class AccountPaymentOrder(models.Model): class AccountPaymentOrder(models.Model):
_inherit = 'account.payment.order' _inherit = "account.payment.order"
def ebics_upload(self): def ebics_upload(self):
self.ensure_one() self.ensure_one()
ctx = self._context.copy() ctx = self._context.copy()
attach = self.env['ir.attachment'].search( attach = self.env["ir.attachment"].search(
[('res_model', '=', self._name), [("res_model", "=", self._name), ("res_id", "=", self.id)]
('res_id', '=', self.id)]) )
if not attach: if not attach:
raise UserError(_( raise UserError(
"This payment order doesn't contains attachements." _(
"\nPlease generate first the Payment Order file first.")) "This payment order doesn't contains attachements."
"\nPlease generate first the Payment Order file first."
)
)
elif len(attach) > 1: elif len(attach) > 1:
raise UserError(_( raise UserError(
"This payment order contains multiple attachments." _(
"\nPlease remove the obsolete attachments or upload " "This payment order contains multiple attachments."
"the payment order file via the " "\nPlease remove the obsolete attachments or upload "
"EBICS Processing > EBICS Upload menu")) "the payment order file via the "
"EBICS Processing > EBICS Upload menu"
)
)
else: else:
origin = _("Payment Order") + ': ' + self.name origin = _("Payment Order") + ": " + self.name
ebics_config = self.env['ebics.config'].search([ ebics_config = self.env["ebics.config"].search(
('journal_ids', '=', self.journal_id.id), [
('state', '=', 'confirm'), ("journal_ids", "=", self.journal_id.id),
]) ("state", "=", "confirm"),
]
)
if not ebics_config: if not ebics_config:
raise UserError(_( raise UserError(
"No active EBICS configuration available " _(
"for the selected bank." "No active EBICS configuration available "
)) "for the selected bank."
)
)
if len(ebics_config) == 1: if len(ebics_config) == 1:
ctx["default_ebics_config_id"] = ebics_config.id ctx["default_ebics_config_id"] = ebics_config.id
ctx.update({ ctx.update(
'default_upload_data': attach.datas, {
'default_upload_fname': attach.name, "default_upload_data": attach.datas,
'origin': origin, "default_upload_fname": attach.name,
'force_comany': self.company_id.id, "origin": origin,
}) "force_comany": self.company_id.id,
ebics_xfer = self.env['ebics.xfer'].with_context(ctx).create({}) }
)
ebics_xfer = self.env["ebics.xfer"].with_context(ctx).create({})
ebics_xfer._onchange_ebics_config_id() ebics_xfer._onchange_ebics_config_id()
ebics_xfer._onchange_upload_data() ebics_xfer._onchange_upload_data()
ebics_xfer._onchange_format_id() ebics_xfer._onchange_format_id()
view = self.env.ref('account_ebics.ebics_xfer_view_form_upload') view = self.env.ref("account_ebics.ebics_xfer_view_form_upload")
act = { act = {
'name': _('EBICS Upload'), "name": _("EBICS Upload"),
'view_type': 'form', "view_type": "form",
'view_mode': 'form', "view_mode": "form",
'res_model': 'ebics.xfer', "res_model": "ebics.xfer",
'view_id': view.id, "view_id": view.id,
'res_id': ebics_xfer.id, "res_id": ebics_xfer.id,
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'target': 'new', "target": "new",
'context': ctx, "context": ctx,
} }
return act return act

View File

@ -1,15 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<data> <data>
<record id="account_payment_order_form" model="ir.ui.view"> <record id="account_payment_order_form" model="ir.ui.view">
<field name="name">account.payment.order.form</field> <field name="name">account.payment.order.form</field>
<field name="model">account.payment.order</field> <field name="model">account.payment.order</field>
<field name="inherit_id" ref="account_payment_order.account_payment_order_form"/> <field name="inherit_id" ref="account_payment_order.account_payment_order_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<button name="open2generated" position="after"> <button name="open2generated" position="after">
<button name="ebics_upload" type="object" states="generated" <button
string="EBICS Upload" class="oe_highlight"/> name="ebics_upload"
type="object"
states="generated"
string="EBICS Upload"
class="oe_highlight"
/>
</button> </button>
</field> </field>
</record> </record>