diff --git a/account_ebics/README.rst b/account_ebics/README.rst
index 73aa9cb..cd02e8e 100644
--- a/account_ebics/README.rst
+++ b/account_ebics/README.rst
@@ -22,9 +22,12 @@ The module depends upon
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 for EBICS 2.x
+and fintech 7.2.7 or higher for EBICS 3.0.
+
+SWIFT 3SKey support requires fintech 6.4 or higher.
+
-SWIFT 3SKey support requires Fintech 6.4 or higher.
|
@@ -35,6 +38,8 @@ We also recommend to consider the installation of the following modules:
- account_ebics_oe
Required if you are running Odoo Enterprise
+
+ Cf. https://github.com/Noviat/account_ebics
|
@@ -42,12 +47,16 @@ We also recommend to consider the installation of the following modules:
This module adds a cron job for the automated import of EBICS files.
+ Cf. https://github.com/Noviat/account_ebics
+
|
- account_ebics_batch_payment
Recommended if you are using the Odoo Enterprise account_batch_payment module
+ Cf. https://github.com/Noviat/account_ebics
+
|
- account_ebics_payment_order
@@ -194,5 +203,4 @@ You can also find this information in the doc folder of this module (file EBICS_
Known Issues / Roadmap
======================
-- add support for EBICS 3.0
- add support to import externally generated keys & certificates (currently only 3SKey signature certificate)
diff --git a/account_ebics/__manifest__.py b/account_ebics/__manifest__.py
index d78be3c..c9a0ac5 100644
--- a/account_ebics/__manifest__.py
+++ b/account_ebics/__manifest__.py
@@ -3,10 +3,10 @@
{
"name": "EBICS banking protocol",
- "version": "14.0.1.0.6",
+ "version": "14.0.1.1.0",
"license": "LGPL-3",
"author": "Noviat",
- "website": "www.noviat.com",
+ "website": "https://www.noviat.com",
"category": "Accounting & Finance",
"depends": ["account"],
"data": [
diff --git a/account_ebics/data/ebics_file_format.xml b/account_ebics/data/ebics_file_format.xml
index 42119b9..05bad93 100644
--- a/account_ebics/data/ebics_file_format.xml
+++ b/account_ebics/data/ebics_file_format.xml
@@ -1,180 +1,215 @@
-
-
+
-
+
-
- camt.052
- down
- C52
- camt.052
- bank to customer account report in format camt.052
- c52.xml
-
+
+ 2
+ camt.052
+ down
+ C52
+ camt.052
+ bank to customer account report in format camt.052
+ c52.xml
+
-
- camt.052
- down
- Z52
- camt.052
- bank to customer account report in format camt.052
- c52.xml
-
+
+ 2
+ camt.052
+ down
+ Z52
+ camt.052
+ bank to customer account report in format camt.052
+ c52.xml
+
-
- camt.053
- down
- C53
- camt.053
- Bank to customer statement report in format camt.053
- c53.xml
-
+
+ 2
+ camt.053
+ down
+ C53
+ camt.053
+ Bank to customer statement report in format camt.053
+ c53.xml
+
-
- camt.053
- down
- Z53
- camt.053
- Bank to customer statement report in format camt.053
- c53.xml
-
+
+ 2
+ camt.053
+ down
+ Z53
+ camt.053
+ Bank to customer statement report in format camt.053
+ c53.xml
+
-
- camt.054
- down
- C54
- camt.054
- Bank to customer debit credit notification in format camt.054
- c52.xml
-
+
+ 2
+ camt.054
+ down
+ C54
+ camt.054
+ Bank to customer debit credit notification in format camt.054
+ c52.xml
+
-
- camt.054
- down
- Z54
- camt.054
- Bank to customer debit credit notification in format camt.054
- c52.xml
-
+
+ 2
+ camt.054
+ down
+ Z54
+ camt.054
+ Bank to customer debit credit notification in format camt.054
+ c52.xml
+
-
- camt.xxx.cfonb120.stm
- down
- FDL
- cfonb120
- Bank to customer statement report in format cfonb120
- cfonb120.dat
-
+
+ 2
+ camt.xxx.cfonb120.stm
+ down
+ FDL
+ cfonb120
+ Bank to customer statement report in format cfonb120
+ cfonb120.dat
+
-
- pain.002
- down
- CDZ
- Payment status report for direct debit in format pain.002
- psr.xml
-
+
+ 2
+ pain.002
+ down
+ CDZ
+ Payment status report for direct debit in format pain.002
+ psr.xml
+
-
- pain.002
- down
- Z01
- pain.002
- Payment status report for direct debit in format pain.002
- psr.xml
-
+
+ 2
+ pain.002
+ down
+ Z01
+ pain.002
+ Payment status report for direct debit in format pain.002
+ psr.xml
+
-
+
+ 3
+ down
+ BTD
+ cfonb120
+ Bank to customer statement report in format cfonb120
+ cfonb120.dat
+ EOP
+ cfonb120
+
-
- pain.xxx.cfonb160.dco
- up
- FUL
- Remises de LCR
- txt
-
+
-
- pain.001.001.03
- up
- CCT
- Payment Order in format pain.001.001.03
- xml
-
+
+ 2
+ pain.xxx.cfonb160.dco
+ up
+ FUL
+ Remises de LCR
+ txt
+
-
- pain.001.001.03
- up
- XE2
- Payment Order in format pain.001.001.03
- xml
-
+
+ 2
+ pain.001.001.03
+ up
+ CCT
+ Payment Order in format pain.001.001.03
+ xml
+
-
- pain.008.001.02.sdd
- up
- CDD
- Sepa Core Direct Debit Order in format pain.008.001.02
- xml
-
+
+ 2
+ pain.001.001.03
+ up
+ XE2
+ Payment Order in format pain.001.001.03
+ xml
+
-
- pain.008.001.02.sdd
- up
- XE3
- Sepa Core Direct Debit Order in format pain.008.001.02
- xml
-
+
+ 2
+ pain.008.001.02.sdd
+ up
+ CDD
+ Sepa Core Direct Debit Order in format pain.008.001.02
+ xml
+
-
- pain.008.001.02.sbb
- up
- CDB
- Sepa Direct Debit (B2B) Order in format pain.008.001.02
- xml
-
+
+ 2
+ pain.008.001.02.sdd
+ up
+ XE3
+ Sepa Core Direct Debit Order in format pain.008.001.02
+ xml
+
-
- pain.008.001.02.sbb
- up
- XE4
- Sepa Direct Debit (B2B) Order in format pain.008.001.02
- xml
-
+
+ 2
+ pain.008.001.02.sbb
+ up
+ CDB
+ Sepa Direct Debit (B2B) Order in format pain.008.001.02
+ xml
+
-
- pain.001.001.02.sct
- up
- FUL
- Payment Order in format pain.001.001.02
- xml
-
+
+ 2
+ pain.008.001.02.sbb
+ up
+ XE4
+ Sepa Direct Debit (B2B) Order in format pain.008.001.02
+ xml
+
+
+
+ 2
+ pain.001.001.02.sct
+ up
+ FUL
+ Payment Order in format pain.001.001.02
+ xml
+
+
+
+ 3
+ up
+ BTU
+ SEPA credit transfer
+ txt
+ SCT
+ pain.001
+ GLB
+
-
diff --git a/account_ebics/doc/2017-03-29-EBICS_V_3.0-FinalVersion.pdf b/account_ebics/doc/2017-03-29-EBICS_V_3.0-FinalVersion.pdf
new file mode 100644
index 0000000..472c58f
Binary files /dev/null and b/account_ebics/doc/2017-03-29-EBICS_V_3.0-FinalVersion.pdf differ
diff --git a/account_ebics/doc/2017-03-29-EBICS_V_3.0_Annex1_ReturnCodes-FinalVersion.pdf b/account_ebics/doc/2017-03-29-EBICS_V_3.0_Annex1_ReturnCodes-FinalVersion.pdf
new file mode 100644
index 0000000..1df4109
Binary files /dev/null and b/account_ebics/doc/2017-03-29-EBICS_V_3.0_Annex1_ReturnCodes-FinalVersion.pdf differ
diff --git a/account_ebics/migrations/14.0.1.1/pre-migration.py b/account_ebics/migrations/14.0.1.1/pre-migration.py
new file mode 100644
index 0000000..4082ce7
--- /dev/null
+++ b/account_ebics/migrations/14.0.1.1/pre-migration.py
@@ -0,0 +1,54 @@
+# Copyright 2009-2022 Noviat.
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+
+def migrate(cr, version):
+ if not version:
+ return
+
+ cr.execute("select id from ebics_config")
+ cfg_ids = [x[0] for x in cr.fetchall()]
+ for cfg_id in cfg_ids:
+ cr.execute(
+ """
+ SELECT aj.company_id
+ FROM account_journal_ebics_config_rel rel
+ JOIN account_journal aj ON rel.account_journal_id = aj.id
+ WHERE ebics_config_id = %s
+ """,
+ (cfg_id,),
+ )
+ new_cpy_ids = [x[0] for x in cr.fetchall()]
+ cr.execute(
+ """
+ SELECT res_company_id
+ FROM ebics_config_res_company_rel
+ WHERE ebics_config_id = %s
+ """,
+ (cfg_id,),
+ )
+ old_cpy_ids = [x[0] for x in cr.fetchall()]
+
+ to_add = []
+ for cid in new_cpy_ids:
+ if cid in old_cpy_ids:
+ old_cpy_ids.remove(cid)
+ else:
+ to_add.append(cid)
+ if old_cpy_ids:
+ cr.execute(
+ """
+ DELETE FROM ebics_config_res_company_rel
+ WHERE res_company_id IN %s
+ """,
+ (tuple(old_cpy_ids),),
+ )
+ if to_add:
+ for cid in to_add:
+ cr.execute(
+ """
+ INSERT INTO ebics_config_res_company_rel(ebics_config_id, res_company_id)
+ VALUES (%s, %s);
+ """,
+ (cfg_id, cid),
+ )
diff --git a/account_ebics/models/account_bank_statement.py b/account_ebics/models/account_bank_statement.py
index e1e1c2b..6b6540f 100644
--- a/account_ebics/models/account_bank_statement.py
+++ b/account_ebics/models/account_bank_statement.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2020 Noviat.
+# Copyright 2009-2022 Noviat.
# License LGPL-3 or later (http://www.gnu.org/licenses/lpgl).
from odoo import fields, models
diff --git a/account_ebics/models/ebics_config.py b/account_ebics/models/ebics_config.py
index 4cc9ec3..4cde65c 100644
--- a/account_ebics/models/ebics_config.py
+++ b/account_ebics/models/ebics_config.py
@@ -22,13 +22,13 @@ class EbicsConfig(models.Model):
_order = "name"
name = fields.Char(
- string="Name",
readonly=True,
states={"draft": [("readonly", False)]},
required=True,
)
journal_ids = fields.Many2many(
comodel_name="account.journal",
+ relation="account_journal_ebics_config_rel",
readonly=True,
states={"draft": [("readonly", False)]},
string="Bank Accounts",
@@ -52,7 +52,11 @@ class EbicsConfig(models.Model):
help="Contact your bank to get the EBICS URL.",
)
ebics_version = fields.Selection(
- selection=[("H003", "H003 (2.4)"), ("H004", "H004 (2.5)")],
+ selection=[
+ ("H003", "H003 (2.4)"),
+ ("H004", "H004 (2.5)"),
+ ("H005", "H005 (3.0)"),
+ ],
string="EBICS protocol version",
readonly=True,
states={"draft": [("readonly", False)]},
@@ -130,7 +134,6 @@ class EbicsConfig(models.Model):
)
state = fields.Selection(
[("draft", "Draft"), ("confirm", "Confirmed")],
- string="State",
default="draft",
required=True,
readonly=True,
@@ -143,11 +146,12 @@ class EbicsConfig(models.Model):
"\nThis number should match the following pattern : "
"[A-Z]{1}[A-Z0-9]{3}",
)
- active = fields.Boolean(string="Active", default=True)
+ active = fields.Boolean(default=True)
company_ids = fields.Many2many(
comodel_name="res.company",
+ relation="ebics_config_res_company_rel",
string="Companies",
- required=True,
+ readonly=True,
help="Companies sharing this EBICS contract.",
)
@@ -179,9 +183,26 @@ class EbicsConfig(models.Model):
)
)
- @api.onchange("journal_ids")
- def _onchange_journal_ids(self):
- self.company_ids = self.journal_ids.mapped("company_id")
+ def write(self, vals):
+ """
+ Due to the multi-company nature of the EBICS config we
+ need to adapt the company_ids in the write method.
+ """
+ if "journal_ids" not in vals:
+ return super().write(vals)
+ for rec in self:
+ old_company_ids = rec.journal_ids.mapped("company_id").ids
+ super(EbicsConfig, rec).write(vals)
+ new_company_ids = rec.journal_ids.mapped("company_id").ids
+ updates = []
+ for cid in new_company_ids:
+ if cid in old_company_ids:
+ old_company_ids.remove(cid)
+ else:
+ updates += [(4, cid)]
+ updates += [(3, x) for x in old_company_ids]
+ super(EbicsConfig, rec).write({"company_ids": updates})
+ return True
def unlink(self):
for ebics_config in self:
diff --git a/account_ebics/models/ebics_file.py b/account_ebics/models/ebics_file.py
index 7e5b390..526a52d 100644
--- a/account_ebics/models/ebics_file.py
+++ b/account_ebics/models/ebics_file.py
@@ -3,6 +3,8 @@
import base64
import logging
+from sys import exc_info
+from traceback import format_exception
from odoo import _, fields, models
from odoo.exceptions import UserError
@@ -46,7 +48,6 @@ class EbicsFile(models.Model):
)
state = fields.Selection(
[("draft", "Draft"), ("done", "Done")],
- string="State",
default="draft",
required=True,
readonly=True,
@@ -93,8 +94,7 @@ class EbicsFile(models.Model):
def process(self):
self.ensure_one()
- ctx = dict(self.env.context, allowed_company_ids=self.env.user.company_ids.ids)
- self = self.with_context(ctx)
+ self = self.with_context(allowed_company_ids=self.env.user.company_ids.ids)
self.note_process = ""
ff_methods = self._file_format_methods()
ff = self.format_id.download_process_method
@@ -159,33 +159,23 @@ class EbicsFile(models.Model):
if raise_if_not_found:
raise UserError(
_(
- "The module to process the '%s' format is not installed "
- "on your system. "
- "\nPlease install module '%s'"
+ "The module to process the '%(ebics_format)s' "
+ "format is not installed on your system. "
+ "\nPlease install module '%(module)s'",
+ ebics_format=self.format_id.name,
+ module=module,
)
- % (self.format_id.name, module)
)
return False
return True
- def _process_result_action(self, res):
+ def _process_result_action(self, res_action):
notifications = []
st_line_ids = []
statement_ids = []
sts_data = []
- if res.get("type") and res["type"] == "ir.actions.client":
- notifications = res["context"].get("notifications", [])
- st_line_ids = res["context"].get("statement_line_ids", [])
- if notifications:
- for notif in notifications:
- parts = []
- for k in ["type", "message", "details"]:
- if notif.get(k):
- msg = "{}: {}".format(k, notif[k])
- parts.append(msg)
- self.note_process += "\n".join(parts)
- self.note_process += "\n"
- self.note_process += "\n"
+ if res_action.get("type") and res_action["type"] == "ir.actions.client":
+ st_line_ids = res_action["context"].get("statement_line_ids", [])
if st_line_ids:
self.flush()
self.env.cr.execute(
@@ -206,10 +196,10 @@ class EbicsFile(models.Model):
)
sts_data = self.env.cr.dictfetchall()
else:
- if res.get("res_id"):
- st_ids = res["res_id"]
+ if res_action.get("res_id"):
+ st_ids = res_action["res_id"]
else:
- st_ids = res["domain"][2]
+ st_ids = res_action["domain"][0][2]
statements = self.env["account.bank.statement"].browse(st_ids)
for statement in statements:
sts_data.append(
@@ -221,7 +211,29 @@ class EbicsFile(models.Model):
}
)
st_cnt = len(sts_data)
+ warning_cnt = error_cnt = 0
+ notifications = res_action["context"].get("notifications", [])
+ if notifications:
+ for notif in notifications:
+ if notif["type"] == "error":
+ error_cnt += 1
+ elif notif["type"] == "warning":
+ warning_cnt += 1
+ parts = [notif[k] for k in notif if k in ("message", "details")]
+ self.note_process += "\n".join(parts)
+ self.note_process += "\n\n"
+ self.note_process += "\n"
+ if error_cnt:
+ self.note_process += (
+ _("Number of errors detected during import: %s: ") % error_cnt
+ )
+ self.note_process += "\n"
+ if warning_cnt:
+ self.note_process += (
+ _("Number of watnings detected during import: %s: ") % warning_cnt
+ )
if st_cnt:
+ self.note_process += "\n\n"
self.note_process += _("%s bank statements have been imported: ") % st_cnt
self.note_process += "\n"
for st_data in sts_data:
@@ -232,7 +244,9 @@ class EbicsFile(models.Model):
)
statement_ids = [x["statement_id"] for x in sts_data]
if statement_ids:
- self.sudo().bank_statement_ids = [(6, 0, statement_ids)]
+ self.sudo().bank_statement_ids = [(4, x) for x in statement_ids]
+ company_ids = self.sudo().bank_statement_ids.mapped("company_id").ids
+ self.company_ids = [(6, 0, company_ids)]
ctx = dict(self.env.context, statement_ids=statement_ids)
module = __name__.split("addons.")[1].split(".")[0]
result_view = self.env.ref("%s.ebics_file_view_form_result" % module)
@@ -279,39 +293,73 @@ class EbicsFile(models.Model):
)
st_lines = b""
transactions = False
- result = {
- "type": "ir.actions.client",
- "tag": "bank_statement_reconciliation_view",
- "context": {
- "statement_line_ids": [],
- "company_ids": self.env.user.company_ids.ids,
- "notifications": [],
- },
- }
- wiz_ctx = dict(self.env.context, active_model="ebics.file")
+ result_action = self.env["ir.actions.act_window"]._for_xml_id(
+ "account.action_bank_statement_tree"
+ )
+ result_action["context"] = safe_eval(result_action["context"])
+ statement_ids = []
+ notifications = []
for i, wiz_vals in enumerate(wiz_vals_list, start=1):
- wiz = self.env[wiz_model].with_context(wiz_ctx).create(wiz_vals)
- res = wiz.import_file_button()
- ctx = res.get("context")
- if res.get("res_model") == "account.bank.statement.import.journal.creation":
- message = _("Error detected while importing statement number %s.\n") % i
- message += _("No financial journal found.")
- details = _("Bank account number: %s") % ctx.get(
- "default_bank_acc_number"
- )
- result["context"]["notifications"].extend(
- [
- {
- "type": "warning",
- "message": message,
- "details": details,
- }
- ]
- )
- continue
- result["context"]["statement_line_ids"].extend(ctx["statement_line_ids"])
- result["context"]["notifications"].extend(ctx["notifications"])
- return self._process_result_action(result)
+ result = {
+ "statement_ids": [],
+ "notifications": [],
+ }
+ statement_filename = wiz_vals["statement_filename"]
+ wiz = (
+ self.env[wiz_model]
+ .with_context(active_model="ebics.file")
+ .create(wiz_vals)
+ )
+ try:
+ with self.env.cr.savepoint():
+ file_data = base64.b64decode(wiz_vals["statement_file"])
+ msg_hdr = _(
+ "{} : Import failed for statement number %(index)s, "
+ "filename %(fn)s:\n",
+ index=i,
+ fn=statement_filename,
+ )
+ wiz.import_single_file(file_data, result)
+ if not result["statement_ids"]:
+ message = msg_hdr.format(_("Warning"))
+ message += _(
+ "You have already imported this file, or this file "
+ "only contains already imported transactions."
+ )
+ notifications += [
+ {
+ "type": "warning",
+ "message": message,
+ }
+ ]
+ else:
+ statement_ids.extend(result["statement_ids"])
+ notifications.extend(result["notifications"])
+
+ except UserError as e:
+ message = msg_hdr.format(_("Error"))
+ message += "".join(e.args)
+ notifications += [
+ {
+ "type": "error",
+ "message": message,
+ }
+ ]
+
+ except Exception:
+ tb = "".join(format_exception(*exc_info()))
+ message = msg_hdr.format(_("Error"))
+ message += tb
+ notifications += [
+ {
+ "type": "error",
+ "message": message,
+ }
+ ]
+
+ result_action["context"]["notifications"] = notifications
+ result_action["domain"] = [("id", "in", statement_ids)]
+ return self._process_result_action(result_action)
@staticmethod
def _unlink_cfonb120(self):
@@ -360,11 +408,12 @@ class EbicsFile(models.Model):
if not found:
raise UserError(
_(
- "The module to process the '%s' format is not installed "
- "on your system. "
- "\nPlease install one of the following modules: \n%s."
+ "The module to process the '%(ebics_format)s' format is "
+ "not installed on your system. "
+ "\nPlease install one of the following modules: \n%(modules)s.",
+ ebics_format=self.format_id.name,
+ modules=", ".join([x[1] for x in modules]),
)
- % (self.format_id.name, ", ".join([x[1] for x in modules]))
)
if _src == "oca":
self._process_camt053_oca()
@@ -386,25 +435,14 @@ class EbicsFile(models.Model):
"notifications": [],
},
}
- 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(active_model="ebics.file").create(wiz_vals)
+ )
res = wiz.import_file_button()
- ctx = res.get("context")
- if res.get("res_model") == "account.bank.statement.import.journal.creation":
- message = _("Error detected while importing statement %s.\n") % self.name
- message += _("No financial journal found.")
- details = _("Bank account number: %s") % ctx.get("default_bank_acc_number")
- result["context"]["notifications"].extend(
- [
- {
- "type": "warning",
- "message": message,
- "details": details,
- }
- ]
- )
- result["context"]["statement_line_ids"].extend(ctx["statement_line_ids"])
- result["context"]["notifications"].extend(ctx["notifications"])
+ result["context"]["statement_line_ids"].extend(
+ res["context"]["statement_line_ids"]
+ )
+ result["context"]["notifications"].extend(res["context"]["notifications"])
return self._process_result_action(result)
def _process_camt053_oe(self):
@@ -418,8 +456,9 @@ class EbicsFile(models.Model):
)
]
}
- 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(active_model="ebics.file").create(wiz_vals)
+ )
res = wiz.import_file()
if res.get("res_model") == "account.bank.statement.import.journal.creation":
if res.get("context"):
diff --git a/account_ebics/models/ebics_file_format.py b/account_ebics/models/ebics_file_format.py
index 8dee0a9..d2ab636 100644
--- a/account_ebics/models/ebics_file_format.py
+++ b/account_ebics/models/ebics_file_format.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2020 Noviat.
+# Copyright 2009-2022 Noviat.
# License LGPL-3 or later (http://www.gnu.org/licenses/lpgl).
from odoo import api, fields, models
@@ -9,9 +9,17 @@ class EbicsFileFormat(models.Model):
_description = "EBICS File Formats"
_order = "type,name,order_type"
+ ebics_version = fields.Selection(
+ selection=[
+ ("2", "2"),
+ ("3", "3"),
+ ],
+ string="EBICS protocol version",
+ required=True,
+ default="2",
+ )
name = fields.Char(
string="Request Type",
- required=True,
help="E.g. camt.xxx.cfonb120.stm, pain.001.001.03.sct.\n"
"Specify camt.052, camt.053, camt.054 for camt "
"Order Types such as C53, Z53, C54, Z54.\n"
@@ -22,9 +30,9 @@ class EbicsFileFormat(models.Model):
selection=[("down", "Download"), ("up", "Upload")], required=True
)
order_type = fields.Char(
- string="Order Type",
required=True,
- help="E.g. C53 (check your EBICS contract).\n"
+ help="EBICS 3.0: BTD (download) or BTU (upload).\n"
+ "EBICS 2.0: E.g. C53 (check your EBICS contract). "
"For most banks in France you should use the "
"format neutral Order Types 'FUL' for upload "
"and 'FDL' for download.",
@@ -40,7 +48,6 @@ class EbicsFileFormat(models.Model):
# move signature_class parameter so that it can be set per EBICS config
signature_class = fields.Selection(
selection=[("E", "Single signature"), ("T", "Transport signature")],
- string="Signature Class",
help="Please doublecheck the security of your Odoo "
"ERP system when using class 'E' to prevent unauthorised "
"users to make supplier payments."
@@ -50,7 +57,48 @@ class EbicsFileFormat(models.Model):
description = fields.Char()
suffix = fields.Char(
required=True,
- help="Specify the filename suffix for this File Format." "\nE.g. c53.xml",
+ help="Specify the filename suffix for this File Format.\nE.g. c53.xml",
+ )
+ # EBICS 3.0 BTF
+ btf_service = fields.Char(
+ string="BTF Service",
+ help="BTF Service Name)\n"
+ "The service code name consisting of 3 alphanumeric characters "
+ "[A-Z0-9] (e.g. SCT, SDD, STM, EOP)",
+ )
+ btf_message = fields.Char(
+ string="BTF Message Name",
+ help="BTF Message Name\n"
+ "The message name consisting of up to 10 alphanumeric characters "
+ "[a-z0-9.] (eg. pain.001, pain.008, camt.053)",
+ )
+ btf_scope = fields.Char(
+ string="BTF Scope",
+ help="Scope of service.\n"
+ "Either an ISO-3166 ALPHA 2 country code or an issuer code "
+ "of 3 alphanumeric characters [A-Z0-9].",
+ )
+ btf_option = fields.Char(
+ string="BTF Option",
+ help="The service option code consisting of 3-10 alphanumeric "
+ "characters [A-Z0-9] (eg. COR, B2B)",
+ )
+ btf_container = fields.Char(
+ string="BTF Container",
+ help="Type of container consisting of 3 characters [A-Z] (eg. XML, ZIP).",
+ )
+ btf_version = fields.Char(
+ string="BTF Version",
+ help="Message version consisting of 2 numeric characters [0-9] (eg. 03).",
+ )
+ btf_variant = fields.Char(
+ string="BTF Variant",
+ help="Message variant consisting of 3 numeric characters [0-9] (eg. 001).",
+ )
+ btf_format = fields.Char(
+ string="BTF Format",
+ help="Message format consisting of 1-4 alphanumeric characters [A-Z0-9] "
+ "(eg. XML, JSON, PDF).",
)
@api.model
@@ -62,3 +110,10 @@ class EbicsFileFormat(models.Model):
def _onchange_type(self):
if self.type == "up":
self.download_process_method = False
+
+ def name_get(self):
+ res = []
+ for rec in self:
+ name = rec.ebics_version == "2" and rec.name or rec.btf_message
+ res.append((rec.id, name))
+ return res
diff --git a/account_ebics/models/ebics_userid.py b/account_ebics/models/ebics_userid.py
index f7fc99f..7df3026 100644
--- a/account_ebics/models/ebics_userid.py
+++ b/account_ebics/models/ebics_userid.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2020 Noviat.
+# Copyright 2009-2022 Noviat.
# License LGPL-3 or later (http://www.gnu.org/licenses/lpgl).
import base64
@@ -14,8 +14,8 @@ from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
# logging.basicConfig(
-# level=logging.DEBUG,
-# format='[%(asctime)s] %(levelname)s - %(name)s: %(message)s')
+# level=logging.DEBUG,
+# format='[%(asctime)s] %(levelname)s - %(name)s: %(message)s')
try:
import fintech
@@ -75,7 +75,6 @@ class EbicsUserID(models.Model):
# Classes A and B are not yet supported.
signature_class = fields.Selection(
selection=[("E", "Single signature"), ("T", "Transport signature")],
- string="Signature Class",
required=True,
default="T",
readonly=True,
@@ -157,12 +156,11 @@ class EbicsUserID(models.Model):
("to_verify", "Verification"),
("active_keys", "Active Keys"),
],
- string="State",
default="draft",
required=True,
readonly=True,
)
- active = fields.Boolean(string="Active", default=True)
+ active = fields.Boolean(default=True)
company_ids = fields.Many2many(
comodel_name="res.company",
string="Companies",
@@ -175,7 +173,9 @@ class EbicsUserID(models.Model):
for rec in self:
keys_dir = rec.ebics_config_id.ebics_keys
rec.ebics_keys_fn = (
- rec.name and keys_dir and (keys_dir + "/" + rec.name + "_keys")
+ rec.name
+ and keys_dir
+ and (keys_dir + "/" + rec.name.replace(" ", "_") + "_keys")
)
@api.depends("ebics_keys_fn")
@@ -243,11 +243,11 @@ class EbicsUserID(models.Model):
partnerid=self.ebics_config_id.ebics_partner,
userid=self.name,
)
- except Exception:
+ except Exception as err:
exctype, value = exc_info()[:2]
error = _("EBICS Initialisation Error:")
error += "\n" + str(exctype) + "\n" + str(value)
- raise UserError(error)
+ raise UserError(error) from err
self.ebics_config_id._check_ebics_keys()
if not os.path.isfile(self.ebics_keys_fn):
@@ -256,7 +256,7 @@ class EbicsUserID(models.Model):
# enable import of all type of certicates: A00x, X002, E002
if self.swift_3skey:
kwargs = {
- self.ebics_config_id.ebics_key_version: base64.decodestring(
+ self.ebics_config_id.ebics_key_version: base64.decodebytes(
self.swift_3skey_certificate
),
}
@@ -265,11 +265,11 @@ class EbicsUserID(models.Model):
keyversion=self.ebics_config_id.ebics_key_version,
bitlength=self.ebics_config_id.ebics_key_bitlength,
)
- except Exception:
+ except Exception as err:
exctype, value = exc_info()[:2]
error = _("EBICS Initialisation Error:")
error += "\n" + str(exctype) + "\n" + str(value)
- raise UserError(error)
+ raise UserError(error) from err
if self.swift_3skey and not self.ebics_key_x509:
raise UserError(
@@ -302,7 +302,7 @@ class EbicsUserID(models.Model):
)
try:
supported_versions = client.HEV()
- if ebics_version not in supported_versions:
+ if supported_versions and ebics_version not in supported_versions:
err_msg = _("EBICS version mismatch.") + "\n"
err_msg += _("Versions supported by your bank:")
for k in supported_versions:
@@ -314,7 +314,7 @@ class EbicsUserID(models.Model):
_logger.info("%s, EBICS INI command, OrderID=%s", self._name, OrderID)
if ebics_version == "H003":
self.ebics_config_id._update_order_number(OrderID)
- except URLError:
+ except URLError as err:
exctype, value = exc_info()[:2]
tb = "".join(format_exception(*exc_info()))
_logger.error(
@@ -323,21 +323,24 @@ class EbicsUserID(models.Model):
tb,
)
raise UserError(
- _("urlopen error:\n url '%s' - %s")
- % (self.ebics_config_id.ebics_url, str(value))
- )
- except EbicsFunctionalError:
+ _(
+ "urlopen error:\n url '%(url)s' - %(val)s",
+ url=self.ebics_config_id.ebics_url,
+ val=str(value),
+ )
+ ) from err
+ except EbicsFunctionalError as err:
e = exc_info()
error = _("EBICS Functional Error:")
error += "\n"
error += "{} (code: {})".format(e[1].message, e[1].code)
- raise UserError(error)
- except EbicsTechnicalError:
+ raise UserError(error) from err
+ except EbicsTechnicalError as err:
e = exc_info()
error = _("EBICS Technical Error:")
error += "\n"
error += "{} (code: {})".format(e[1].message, e[1].code)
- raise UserError(error)
+ raise UserError(error) from err
# Send the public authentication and encryption keys to the bank.
if ebics_version == "H003":
@@ -411,25 +414,25 @@ class EbicsUserID(models.Model):
userid=self.name,
)
client = EbicsClient(bank, user, version=self.ebics_config_id.ebics_version)
- except Exception:
+ except Exception as err:
exctype, value = exc_info()[:2]
error = _("EBICS Initialisation Error:")
error += "\n" + str(exctype) + "\n" + str(value)
- raise UserError(error)
+ raise UserError(error) from err
try:
public_bank_keys = client.HPB()
- except EbicsFunctionalError:
+ except EbicsFunctionalError as err:
e = exc_info()
error = _("EBICS Functional Error:")
error += "\n"
error += "{} (code: {})".format(e[1].message, e[1].code)
- raise UserError(error)
- except Exception:
+ raise UserError(error) from err
+ except Exception as err:
exctype, value = exc_info()[:2]
error = _("EBICS Initialisation Error:")
error += "\n" + str(exctype) + "\n" + str(value)
- raise UserError(error)
+ raise UserError(error) from err
public_bank_keys = public_bank_keys.encode()
tmp_dir = os.path.normpath(self.ebics_config_id.ebics_files + "/tmp")
@@ -442,7 +445,7 @@ class EbicsUserID(models.Model):
)
self.write(
{
- "ebics_public_bank_keys": base64.encodestring(public_bank_keys),
+ "ebics_public_bank_keys": base64.encodebytes(public_bank_keys),
"ebics_public_bank_keys_fn": fn,
"state": "to_verify",
}
diff --git a/account_ebics/static/description/index.html b/account_ebics/static/description/index.html
index f4667ee..20511dc 100644
--- a/account_ebics/static/description/index.html
+++ b/account_ebics/static/description/index.html
@@ -379,8 +379,9 @@ ul.auto-toc {
https://pypi.python.org/pypi/cryptography
Remark:
-The EBICS 'Test Mode' for uploading orders requires Fintech 4.3.4 or higher.
-SWIFT 3SKey support requires Fintech 6.4 or higher.
+The EBICS 'Test Mode' for uploading orders requires fintech 4.3.4 or higher for EBICS 2.x
+and fintech 7.2.7 or higher for EBICS 3.0.
+SWIFT 3SKey support requires fintech 6.4 or higher.
@@ -391,6 +392,7 @@ ul.auto-toc {
@@ -399,6 +401,7 @@ ul.auto-toc {
@@ -407,6 +410,7 @@ ul.auto-toc {
@@ -547,7 +551,6 @@ You can also find this information in the doc folder of this module (file EBICS_
Known Issues / Roadmap
-- add support for EBICS 3.0
- add support to import externally generated keys & certificates (currently only 3SKey signature certificate)
diff --git a/account_ebics/views/ebics_config_views.xml b/account_ebics/views/ebics_config_views.xml
index d407af5..f7220d9 100644
--- a/account_ebics/views/ebics_config_views.xml
+++ b/account_ebics/views/ebics_config_views.xml
@@ -5,7 +5,7 @@
ebics.config.tree
ebics.config
-
+
@@ -66,10 +66,10 @@
+
-
diff --git a/account_ebics/views/ebics_file_format_views.xml b/account_ebics/views/ebics_file_format_views.xml
index 03dd598..e677d3c 100644
--- a/account_ebics/views/ebics_file_format_views.xml
+++ b/account_ebics/views/ebics_file_format_views.xml
@@ -5,7 +5,8 @@
ebics.file.format.tree
ebics.file.format
-
+
+
@@ -22,6 +23,7 @@