diff --git a/README.md b/README.md
index c5a5ead17..300bc3a5b 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ Available addons
addon | version | maintainers | summary
--- | --- | --- | ---
[edi_component_oca](edi_component_oca/) | 19.0.1.1.0 |
| Allow to use Connector as a source in EDI
-[edi_core_oca](edi_core_oca/) | 19.0.1.2.0 |
| Define backends, exchange types, exchange records, basic automation and views for handling EDI exchanges.
+[edi_core_oca](edi_core_oca/) | 19.0.1.2.1 |
| Define backends, exchange types, exchange records, basic automation and views for handling EDI exchanges.
[edi_endpoint_oca](edi_endpoint_oca/) | 19.0.1.1.1 | | Base module allowing configuration of custom endpoints for EDI framework.
[edi_product_oca](edi_product_oca/) | 19.0.1.0.0 | | EDI framework configuration and base logic for products and units of measure
[edi_purchase_oca](edi_purchase_oca/) | 19.0.1.0.0 | | Define EDI Configuration for Purchase Orders
diff --git a/edi_core_oca/README.rst b/edi_core_oca/README.rst
index dfe98e764..c1bd89935 100644
--- a/edi_core_oca/README.rst
+++ b/edi_core_oca/README.rst
@@ -11,7 +11,7 @@ EDI
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:46139d4cc31684944b8784e2503633ab59a96d8b072b42ee5ab30cd8f2177930
+ !! source digest: sha256:de90b9bf70f5e4c8e52326e5549c47a2a3d9b91843ffb0a101d442d6fce3f8c3
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/edi_core_oca/__manifest__.py b/edi_core_oca/__manifest__.py
index 5496b6981..7537474e2 100644
--- a/edi_core_oca/__manifest__.py
+++ b/edi_core_oca/__manifest__.py
@@ -9,7 +9,7 @@
Define backends, exchange types, exchange records,
basic automation and views for handling EDI exchanges.
""",
- "version": "19.0.1.2.0",
+ "version": "19.0.1.2.1",
"website": "https://github.com/OCA/edi-framework",
"development_status": "Beta",
"license": "LGPL-3",
diff --git a/edi_core_oca/i18n/it.po b/edi_core_oca/i18n/it.po
index 86758cb0d..b039537a1 100644
--- a/edi_core_oca/i18n/it.po
+++ b/edi_core_oca/i18n/it.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-05-25 22:46+0000\n"
+"PO-Revision-Date: 2026-06-05 09:46+0000\n"
"Last-Translator: mymage \n"
"Language-Team: none\n"
"Language: it\n"
@@ -157,7 +157,7 @@ msgstr ""
#. module: edi_core_oca
#: model:ir.model.fields,field_description:edi_core_oca.field_edi_exchange_type__exchange_record_count
msgid "# Exchange Records"
-msgstr ""
+msgstr "N° record scambio"
#. module: edi_core_oca
#: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_exchange_consumer_mixin_buttons
@@ -319,7 +319,7 @@ msgstr "Applica a questo modello"
#. module: edi_core_oca
#: model:ir.actions.server,name:edi_core_oca.ir_cron_archive_old_edi_records_ir_actions_server
msgid "Archive Old EDI Exchange Records"
-msgstr ""
+msgstr "Archivia i vecchi record EDI"
#. module: edi_core_oca
#: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_backend_view_form
@@ -352,12 +352,12 @@ msgstr ""
#. module: edi_core_oca
#: model:ir.model.fields,field_description:edi_core_oca.field_edi_backend__auto_archive_records_after_days
msgid "Auto-archive records after (days)"
-msgstr ""
+msgstr "Auto archivia i record dopo (giorni)"
#. module: edi_core_oca
#: model:ir.model.fields,field_description:edi_core_oca.field_edi_backend__auto_delete_records_after_days
msgid "Auto-delete archived records after (days)"
-msgstr ""
+msgstr "Auto cancella i record archiviati dopo (giorni)"
#. module: edi_core_oca
#: model:ir.model.fields,help:edi_core_oca.field_edi_backend__auto_archive_records_after_days
@@ -365,6 +365,8 @@ msgid ""
"Automatically archive EDI exchange records after X days. Set to <= 0 to "
"disable auto-archiving."
msgstr ""
+"Archivia automaticamente i record di scambio EDI dopo X giorni. Impostare "
+"inferiore o uguale a 0 per disabilitare l'auto archiviazione."
#. module: edi_core_oca
#: model:ir.model.fields,help:edi_core_oca.field_edi_backend__auto_delete_records_after_days
@@ -372,6 +374,8 @@ msgid ""
"Automatically delete archived EDI exchange records after X days. Set to <= 0 "
"to disable auto-deletion."
msgstr ""
+"Cancella automaticamente i record di scambio EDI archiviati dopo X giorni. "
+"Impostare inferiore o uguale a 0 per disabilitare l'auto cancellazione."
#. module: edi_core_oca
#: model:ir.model.fields,field_description:edi_core_oca.field_edi_configuration__backend_id
@@ -528,7 +532,7 @@ msgstr "Gestore errore decodifica"
#. module: edi_core_oca
#: model:ir.actions.server,name:edi_core_oca.ir_cron_delete_old_archived_edi_records_ir_actions_server
msgid "Delete Old Archived EDI Exchange Records"
-msgstr ""
+msgstr "Cancella i vecchi record di scambio EDI archiviati"
#. module: edi_core_oca
#: model:ir.model.fields,help:edi_core_oca.field_edi_configuration__description
@@ -1547,7 +1551,7 @@ msgstr "Record ID=%d non è previsto che sia inviato!"
#. module: edi_core_oca
#: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_backend_view_form
msgid "Records retention"
-msgstr ""
+msgstr "Cancellazione record"
#. module: edi_core_oca
#: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_exchange_record_view_form
@@ -1808,6 +1812,10 @@ msgid ""
"waiting for the cron to pass by. Requires auto generate flag to be active as "
"well. The cron will skip these records unless forced."
msgstr ""
+"Quando attivi, i record di questo tipo verranno elaborati immediatamente, "
+"senza attendere il completamento del cron. Richiede che anche il flag di "
+"generazione automatica sia attivo. Il cron ignorerà questi record, a meno "
+"che non venga forzato."
#. module: edi_core_oca
#: model:ir.model.fields,help:edi_core_oca.field_edi_exchange_consumer_mixin__edi_disable_auto
diff --git a/edi_core_oca/models/edi_backend.py b/edi_core_oca/models/edi_backend.py
index 6a065ae34..0f004b822 100644
--- a/edi_core_oca/models/edi_backend.py
+++ b/edi_core_oca/models/edi_backend.py
@@ -121,6 +121,7 @@ def exchange_generate(self, exchange_record, store=True, force=False, **kw):
:param kw: keyword args to be propagated to output generate handler
"""
self.ensure_one()
+ old_state = exchange_record.edi_exchange_state
if force and exchange_record.exchange_file:
# Remove file to regenerate
exchange_record.exchange_file = False
@@ -148,7 +149,6 @@ def exchange_generate(self, exchange_record, store=True, force=False, **kw):
traceback = _get_exception_traceback()
error = _get_exception_msg(err)
state = "validate_error"
- message = exchange_record._exchange_status_message("validate_ko")
exchange_record.update(
{
"edi_exchange_state": state,
@@ -156,6 +156,15 @@ def exchange_generate(self, exchange_record, store=True, force=False, **kw):
"exchange_error_traceback": traceback,
}
)
+ if old_state != state:
+ exchange_record._notify_error("validate_ko")
+ # At this point `message` still holds the "generate_ok" success
+ # text set before validation ran. Generation succeeded but
+ # validation failed, so clear it: `_notify_error` has already
+ # posted the validation error (and fired the error event), and
+ # we must not let `notify_action_complete` below post the stale
+ # success message on top of it.
+ message = None
exchange_record.notify_action_complete("generate", message=message)
return message
@@ -228,7 +237,7 @@ def exchange_send(self, exchange_record):
check = self._output_check_send(exchange_record)
if not check:
return self._failed_output_check_send_msg()
- state = exchange_record.edi_exchange_state
+ old_state = state = exchange_record.edi_exchange_state
error = traceback = False
message = None
res = ""
@@ -248,7 +257,6 @@ def exchange_send(self, exchange_record):
traceback = _get_exception_traceback()
error = _get_exception_msg(err)
state = "output_error_on_send"
- message = exchange_record._exchange_status_message("send_ko")
res = f"Error: {error}"
_logger.debug(
"%s send failed. Marked as errored.", exchange_record.identifier
@@ -281,6 +289,8 @@ def exchange_send(self, exchange_record):
"exchanged_on": fields.Datetime.now(),
}
)
+ if old_state != state and state == "output_error_on_send":
+ exchange_record._notify_error("send_ko")
exchange_record.notify_action_complete("send", message=message)
return res
@@ -514,7 +524,7 @@ def exchange_receive(self, exchange_record):
check = self._exchange_receive_check(exchange_record)
if not check:
return "Nothing to do. Likely already received."
- state = exchange_record.edi_exchange_state
+ old_state = state = exchange_record.edi_exchange_state
error = traceback = False
message = None
content = None
@@ -528,7 +538,6 @@ def exchange_receive(self, exchange_record):
traceback = _get_exception_traceback()
error = _get_exception_msg(err)
state = "validate_error"
- message = exchange_record._exchange_status_message("validate_ko")
res = f"Validation error: {error}"
except self._swallable_exceptions() as err:
if self.env.context.get("_edi_receive_break_on_error"):
@@ -536,7 +545,6 @@ def exchange_receive(self, exchange_record):
traceback = _get_exception_traceback()
error = _get_exception_msg(err)
state = "input_receive_error"
- message = exchange_record._exchange_status_message("receive_ko")
res = f"Input error: {error}"
except (OperationalError, IntegrityError):
# We don't want the finally block to be executed in this case as
@@ -561,6 +569,10 @@ def exchange_receive(self, exchange_record):
"exchanged_on": fields.Datetime.now(),
}
)
+ if old_state != state and state == "input_receive_error":
+ exchange_record._notify_error("receive_ko")
+ if old_state != state and state == "validate_error":
+ exchange_record._notify_error("validate_ko")
exchange_record.notify_action_complete("receive", message=message)
return res
diff --git a/edi_core_oca/static/description/index.html b/edi_core_oca/static/description/index.html
index 03d5c155b..b06702882 100644
--- a/edi_core_oca/static/description/index.html
+++ b/edi_core_oca/static/description/index.html
@@ -372,7 +372,7 @@ EDI
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:46139d4cc31684944b8784e2503633ab59a96d8b072b42ee5ab30cd8f2177930
+!! source digest: sha256:de90b9bf70f5e4c8e52326e5549c47a2a3d9b91843ffb0a101d442d6fce3f8c3
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Base EDI backend.
diff --git a/edi_core_oca/tests/common.py b/edi_core_oca/tests/common.py
index fe9bdb9d1..8f74be261 100644
--- a/edi_core_oca/tests/common.py
+++ b/edi_core_oca/tests/common.py
@@ -100,3 +100,26 @@ def setUpClass(cls):
super().setUpClass()
cls._setup_env()
cls._setup_records()
+
+ def _make_global_error_conf(self, exchange_type):
+ """Register a global ``edi.configuration`` bound to the
+ ``on_edi_exchange_error`` event.
+
+ Its snippet writes a marker on the configuration so a test can assert
+ the error event actually fired. This is the observable behaviour that
+ distinguishes an errored exchange (which must notify, e.g. create the
+ activities handled by ``edi_notification_oca``) from one that merely
+ posts a chatter message via ``notify_action_complete``.
+ """
+ trigger = self.env.ref("edi_core_oca.edi_config_trigger_record_error")
+ return self.env["edi.configuration"].create(
+ {
+ "name": "Test notify on error",
+ "active": True,
+ "backend_id": self.backend.id,
+ "type_id": exchange_type.id,
+ "trigger_id": trigger.id,
+ "is_global": True,
+ "snippet_do": "conf.write({'description': 'error-event-fired'})",
+ }
+ )
diff --git a/edi_core_oca/tests/test_backend_input.py b/edi_core_oca/tests/test_backend_input.py
index 44065c927..eb9a04752 100644
--- a/edi_core_oca/tests/test_backend_input.py
+++ b/edi_core_oca/tests/test_backend_input.py
@@ -77,6 +77,16 @@ def test_receive_no_allow_empty_file_record(self):
self.record, [{"edi_exchange_state": "input_receive_error"}]
)
+ def test_receive_no_allow_empty_file_triggers_notify_error(self):
+ self.record.edi_exchange_state = "input_pending"
+ conf = self._make_global_error_conf(self.record.type_id)
+ self.backend.with_context(
+ fake_output="", _edi_receive_break_on_error=False
+ ).exchange_receive(self.record)
+ # The error event must fire so downstream notifications (e.g.
+ # edi_notification_oca activities) are triggered.
+ self.assertEqual(conf.description, "error-event-fired")
+
def test_receive_allow_empty_file_record(self):
self.record.edi_exchange_state = "input_pending"
self.record.type_id.allow_empty_files_on_receive = True
diff --git a/edi_core_oca/tests/test_backend_output.py b/edi_core_oca/tests/test_backend_output.py
index 39f2d57a8..2b5ea7d41 100644
--- a/edi_core_oca/tests/test_backend_output.py
+++ b/edi_core_oca/tests/test_backend_output.py
@@ -100,6 +100,17 @@ def test_send_record_with_error(self):
"OOPS! Something went wrong :(", self.record.exchange_error_traceback
)
+ def test_send_record_with_error_triggers_notify_error(self):
+ self.record.write({"edi_exchange_state": "output_pending"})
+ self.record._set_file_content(f"TEST {self.record.id}")
+ conf = self._make_global_error_conf(self.record.type_id)
+ self.record.with_context(
+ test_break_send="OOPS! Something went wrong :("
+ ).action_exchange_send()
+ # The error event must fire so downstream notifications (e.g.
+ # edi_notification_oca activities) are triggered.
+ self.assertEqual(conf.description, "error-event-fired")
+
def test_send_invalid_direction(self):
vals = {
"model": self.partner._name,
diff --git a/edi_core_oca/tests/test_backend_validate.py b/edi_core_oca/tests/test_backend_validate.py
index d3c585f8b..3c082e454 100644
--- a/edi_core_oca/tests/test_backend_validate.py
+++ b/edi_core_oca/tests/test_backend_validate.py
@@ -85,6 +85,17 @@ def test_receive_validate_record_error(self):
)
self.assertIn("Data seems wrong!", self.record_in.exchange_error_traceback)
+ def test_receive_validate_record_error_triggers_notify_error(self):
+ self.record_in.write({"edi_exchange_state": "input_pending"})
+ exc = EDIValidationError("Data seems wrong!")
+ conf = self._make_global_error_conf(self.record_in.type_id)
+ self.backend.with_context(test_break_input_validate=exc).exchange_receive(
+ self.record_in
+ )
+ # The error event must fire so downstream notifications (e.g.
+ # edi_notification_oca activities) are triggered.
+ self.assertEqual(conf.description, "error-event-fired")
+
def test_generate_validate_record(self):
self.record_out.write({"edi_exchange_state": "new"})
self.backend.exchange_generate(self.record_out)
@@ -119,6 +130,17 @@ def test_generate_validate_record_error(self):
)
self.assertIn("Data seems wrong!", self.record_out.exchange_error_traceback)
+ def test_generate_validate_record_error_triggers_notify_error(self):
+ self.record_out.write({"edi_exchange_state": "new"})
+ exc = EDIValidationError("Data seems wrong!")
+ conf = self._make_global_error_conf(self.record_out.type_id)
+ self.backend.with_context(test_break_output_validate=exc).exchange_generate(
+ self.record_out
+ )
+ # The error event must fire so downstream notifications (e.g.
+ # edi_notification_oca activities) are triggered.
+ self.assertEqual(conf.description, "error-event-fired")
+
def test_validate_record_error_regenerate(self):
self.record_out.write({"edi_exchange_state": "new"})
exc = EDIValidationError("Data seems wrong!")
diff --git a/edi_endpoint_oca/i18n/it.po b/edi_endpoint_oca/i18n/it.po
index 18eeab1e1..aaa9dda9e 100644
--- a/edi_endpoint_oca/i18n/it.po
+++ b/edi_endpoint_oca/i18n/it.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-05-25 22:46+0000\n"
+"PO-Revision-Date: 2026-06-05 09:46+0000\n"
"Last-Translator: mymage \n"
"Language-Team: none\n"
"Language: it\n"
@@ -71,7 +71,7 @@ msgstr "Documenti esempio codice"
#. module: edi_endpoint_oca
#: model:ir.model.fields,help:edi_endpoint_oca.field_edi_endpoint__cors
msgid "Comma-separated list of allowed origins"
-msgstr ""
+msgstr "Elenco separato da virgola delle origini consentite"
#. module: edi_endpoint_oca
#: model:ir.model.fields,field_description:edi_endpoint_oca.field_edi_endpoint__company_id
@@ -81,7 +81,7 @@ msgstr "Azienda"
#. module: edi_endpoint_oca
#: model:ir.model.fields,field_description:edi_endpoint_oca.field_edi_endpoint__cors
msgid "Cors"
-msgstr ""
+msgstr "Cors"
#. module: edi_endpoint_oca
#. odoo-python