From b42ba0aea8c9e1891832dc9e79a4a71a0a8b06fe Mon Sep 17 00:00:00 2001 From: Ricardoalso Date: Mon, 23 Feb 2026 09:53:48 +0100 Subject: [PATCH 01/42] [FIX] edi_core_oca: handle OperationalError and IntegrityError exceptions to prevent finally block execution in aborted transaction state --- edi_core_oca/models/edi_backend.py | 83 ++++++++++++++-------- edi_core_oca/tests/test_backend_input.py | 10 +++ edi_core_oca/tests/test_backend_output.py | 11 +++ edi_core_oca/tests/test_backend_process.py | 10 +++ 4 files changed, 84 insertions(+), 30 deletions(-) diff --git a/edi_core_oca/models/edi_backend.py b/edi_core_oca/models/edi_backend.py index 47d9c3db7..e7aa9b968 100644 --- a/edi_core_oca/models/edi_backend.py +++ b/edi_core_oca/models/edi_backend.py @@ -10,6 +10,8 @@ import traceback from io import StringIO +from psycopg2 import IntegrityError, OperationalError + from odoo import exceptions, fields, models from odoo.exceptions import UserError @@ -237,6 +239,11 @@ def exchange_send(self, exchange_record): _logger.debug( "%s send failed. Marked as errored.", exchange_record.identifier ) + except (OperationalError, IntegrityError): + # We don't want the finally block to be executed in this case as + # the cursor is already in an aborted state and any query will fail. + res = "__sql_error__" + raise else: # TODO: maybe the send handler should return desired message and state message = exchange_record._exchange_status_message("send_ok") @@ -248,16 +255,18 @@ def exchange_send(self, exchange_record): ) res = message finally: - exchange_record.write( - { - "edi_exchange_state": state, - "exchange_error": error, - "exchange_error_traceback": traceback, - # FIXME: this should come from _compute_exchanged_on - # but somehow it's failing in send tests (in record tests it works). - "exchanged_on": fields.Datetime.now(), - } - ) + if res != "__sql_error__": + exchange_record.write( + { + "edi_exchange_state": state, + "exchange_error": error, + "exchange_error_traceback": traceback, + # FIXME: this should come from _compute_exchanged_on + # but somehow it's failing in send tests + # (in record tests it works). + "exchanged_on": fields.Datetime.now(), + } + ) exchange_record.notify_action_complete("send", message=message) return res @@ -445,20 +454,27 @@ def exchange_process(self, exchange_record): error = _get_exception_msg(err) state = "input_processed_error" res = f"Error: {error}" + except (OperationalError, IntegrityError): + # We don't want the finally block to be executed in this case as + # the cursor is already in an aborted state and any query will fail. + res = "__sql_error__" + raise else: error = traceback = None state = "input_processed" finally: - exchange_record.write( - { - "edi_exchange_state": state, - "exchange_error": error, - "exchange_error_traceback": traceback, - # FIXME: this should come from _compute_exchanged_on - # but somehow it's failing in send tests (in record tests it works). - "exchanged_on": fields.Datetime.now(), - } - ) + if res != "__sql_error__": + exchange_record.write( + { + "edi_exchange_state": state, + "exchange_error": error, + "exchange_error_traceback": traceback, + # FIXME: this should come from _compute_exchanged_on + # but somehow it's failing in send tests + # (in record tests it works). + "exchanged_on": fields.Datetime.now(), + } + ) if ( state == "input_processed_error" and old_state != "input_processed_error" @@ -506,22 +522,29 @@ def exchange_receive(self, exchange_record): 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 + # the cursor is already in an aborted state and any query will fail. + res = "__sql_error__" + raise else: message = exchange_record._exchange_status_message("receive_ok") error = traceback = None state = "input_received" res = message finally: - exchange_record.write( - { - "edi_exchange_state": state, - "exchange_error": error, - "exchange_error_traceback": traceback, - # FIXME: this should come from _compute_exchanged_on - # but somehow it's failing in send tests (in record tests it works). - "exchanged_on": fields.Datetime.now(), - } - ) + if res != "__sql_error__": + exchange_record.write( + { + "edi_exchange_state": state, + "exchange_error": error, + "exchange_error_traceback": traceback, + # FIXME: this should come from _compute_exchanged_on + # but somehow it's failing in send tests + # (in record tests it works). + "exchanged_on": fields.Datetime.now(), + } + ) exchange_record.notify_action_complete("receive", message=message) return res diff --git a/edi_core_oca/tests/test_backend_input.py b/edi_core_oca/tests/test_backend_input.py index 89d7fe13f..0ca5e2e5b 100644 --- a/edi_core_oca/tests/test_backend_input.py +++ b/edi_core_oca/tests/test_backend_input.py @@ -3,6 +3,7 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). from odoo_test_helper import FakeModelLoader +from psycopg2 import OperationalError from .common import EDIBackendCommonTestCase @@ -78,3 +79,12 @@ def test_receive_allow_empty_file_record(self): # Check the record self.assertEqual(self.record._get_file_content(), "") self.assertRecordValues(self.record, [{"edi_exchange_state": "input_received"}]) + + def test_receive_record_with_operational_error(self): + self.record.edi_exchange_state = "input_pending" + with self.assertRaises(OperationalError): + self.backend.with_context( + test_break_receive=OperationalError("SQL error") + ).exchange_receive(self.record) + self.assertRecordValues(self.record, [{"edi_exchange_state": "input_pending"}]) + self.assertFalse(self.record.exchange_error) diff --git a/edi_core_oca/tests/test_backend_output.py b/edi_core_oca/tests/test_backend_output.py index 69bd07fe8..2d5e49810 100644 --- a/edi_core_oca/tests/test_backend_output.py +++ b/edi_core_oca/tests/test_backend_output.py @@ -7,6 +7,7 @@ from freezegun import freeze_time from odoo_test_helper import FakeModelLoader +from psycopg2 import OperationalError from odoo import fields, tools from odoo.exceptions import UserError @@ -122,3 +123,13 @@ def test_send_not_generated_record(self): err.exception.args[0], "Record ID=%d has no file to send!" % record.id ) mocked.assert_not_called() + + def test_send_record_with_operational_error(self): + self.record.write({"edi_exchange_state": "output_pending"}) + self.record._set_file_content("TEST %d" % self.record.id) + with self.assertRaises(OperationalError): + self.backend.with_context( + test_break_send=OperationalError("SQL error") + ).exchange_send(self.record) + self.assertRecordValues(self.record, [{"edi_exchange_state": "output_pending"}]) + self.assertFalse(self.record.exchange_error) diff --git a/edi_core_oca/tests/test_backend_process.py b/edi_core_oca/tests/test_backend_process.py index 913abfb6d..de259115e 100644 --- a/edi_core_oca/tests/test_backend_process.py +++ b/edi_core_oca/tests/test_backend_process.py @@ -6,6 +6,7 @@ from freezegun import freeze_time from odoo_test_helper import FakeModelLoader +from psycopg2 import IntegrityError from odoo import fields from odoo.exceptions import UserError @@ -103,4 +104,13 @@ def test_process_outbound_record(self): with self.assertRaises(UserError): record.action_exchange_process() + def test_process_record_with_integrity_error(self): + self.record.write({"edi_exchange_state": "input_received"}) + with self.assertRaises(IntegrityError): + self.backend.with_context( + test_break_process=IntegrityError("SQL error") + ).exchange_process(self.record) + self.assertRecordValues(self.record, [{"edi_exchange_state": "input_received"}]) + self.assertFalse(self.record.exchange_error) + # TODO: test ack file are processed From 581be1c4b3355c3ced86d6d28d64f5b41cb61f1e Mon Sep 17 00:00:00 2001 From: Arnau Date: Thu, 12 Mar 2026 11:18:28 +0100 Subject: [PATCH 02/42] [REF] edi_core: move back ``_trigger_edi_event_make_name`` to core --- edi_component_oca/models/edi_exchange_record.py | 6 ------ edi_core_oca/models/edi_exchange_record.py | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/edi_component_oca/models/edi_exchange_record.py b/edi_component_oca/models/edi_exchange_record.py index 551d1fa6b..c37c17f93 100644 --- a/edi_component_oca/models/edi_exchange_record.py +++ b/edi_component_oca/models/edi_exchange_record.py @@ -8,12 +8,6 @@ class EdiExchangeRecord(models.Model): _inherit = "edi.exchange.record" - def _trigger_edi_event_make_name(self, name, suffix=None): - return "on_edi_exchange_{name}{suffix}".format( - name=name, - suffix=("_" + suffix) if suffix else "", - ) - def _trigger_edi_event(self, name, suffix=None, target=None, **kw): """Trigger a component event linked to this backend and edi exchange.""" name = self._trigger_edi_event_make_name(name, suffix=suffix) diff --git a/edi_core_oca/models/edi_exchange_record.py b/edi_core_oca/models/edi_exchange_record.py index a87b4506e..4cc9d86b7 100644 --- a/edi_core_oca/models/edi_exchange_record.py +++ b/edi_core_oca/models/edi_exchange_record.py @@ -536,6 +536,12 @@ def _trigger_edi_event(self, name, suffix=None, target=None, **kw): """Hook to be implemented in other modules""" pass + def _trigger_edi_event_make_name(self, name, suffix=None): + return "on_edi_exchange_{name}{suffix}".format( + name=name, + suffix=("_" + suffix) if suffix else "", + ) + def _notify_done(self): self._notify_related_record(self._exchange_status_message("process_ok")) self._trigger_edi_event("done") From f91e4244b7c7d0000c7552f2b658fec72a0da4b3 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 10:40:56 +0200 Subject: [PATCH 03/42] [IMP[ edi_core: add option for global edi.configuration Global configurations can be used to catch generic events not bound to specific partner releations. As such, they are the core framework replacement for component events. Co-authored-by: Arnau --- edi_core_oca/models/edi_configuration.py | 7 +++++++ edi_core_oca/views/edi_configuration_views.xml | 1 + 2 files changed, 8 insertions(+) diff --git a/edi_core_oca/models/edi_configuration.py b/edi_core_oca/models/edi_configuration.py index 93d7412f3..795d141ce 100644 --- a/edi_core_oca/models/edi_configuration.py +++ b/edi_core_oca/models/edi_configuration.py @@ -67,6 +67,13 @@ class EdiConfiguration(models.Model): help="""Used to do something specific here. Receives: operation, edi_action, vals, old_vals.""", ) + # You can use this to avoid component events ;) + is_global = fields.Boolean( + string="Global Configuration", + help="If checked, this configuration will be executed for all records, " + "regardless of the partner relation.", + default=False, + ) @api.constrains("backend_id", "type_id") def _constrains_backend(self): diff --git a/edi_core_oca/views/edi_configuration_views.xml b/edi_core_oca/views/edi_configuration_views.xml index 053db7764..674e88f8b 100644 --- a/edi_core_oca/views/edi_configuration_views.xml +++ b/edi_core_oca/views/edi_configuration_views.xml @@ -74,6 +74,7 @@ name="model_id" options="{'no_create': True, 'no_create_edit': True}" /> + From 4d28237b9b753034c27ef4aedf33eef68d3d3301 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 11:02:55 +0200 Subject: [PATCH 04/42] [IMP] edi_core: trigger events w/ global edi.conf Co-authored-by: Arnau --- edi_core_oca/data/edi_configuration.xml | 12 ++++++++++++ edi_core_oca/models/edi_exchange_record.py | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/edi_core_oca/data/edi_configuration.xml b/edi_core_oca/data/edi_configuration.xml index 565919c74..867212198 100644 --- a/edi_core_oca/data/edi_configuration.xml +++ b/edi_core_oca/data/edi_configuration.xml @@ -15,6 +15,18 @@ on_record_write Trigger when a record is updated + + + On record exchange done + on_edi_exchange_done + Trigger when a record exchange is done + + + On record exchange error + on_edi_exchange_error + Trigger when a record exchange has an error + + Send via email diff --git a/edi_core_oca/models/edi_exchange_record.py b/edi_core_oca/models/edi_exchange_record.py index 4cc9d86b7..ee86c86d3 100644 --- a/edi_core_oca/models/edi_exchange_record.py +++ b/edi_core_oca/models/edi_exchange_record.py @@ -533,8 +533,18 @@ def _notify_related_record(self, message, level="info"): rec._notify_related_record(message, level) def _trigger_edi_event(self, name, suffix=None, target=None, **kw): - """Hook to be implemented in other modules""" - pass + event_name = self._trigger_edi_event_make_name(name, suffix) + target = target or self + + global_configs = self.env["edi.configuration"].search( + [ + ("trigger", "=", event_name), + ("is_global", "=", True), + ] + ) + + for conf in global_configs: + conf.edi_exec_snippet_do(target, **kw) def _trigger_edi_event_make_name(self, name, suffix=None): return "on_edi_exchange_{name}{suffix}".format( From 0f28c663648ba7e9265b42bd00356a51182bc213 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 11:15:31 +0200 Subject: [PATCH 05/42] [IMP] edi_core: improve global edi.conf * full matching by type, backend and model * full test coverage --- edi_core_oca/models/edi_configuration.py | 29 +++ edi_core_oca/models/edi_exchange_record.py | 9 +- edi_core_oca/tests/test_edi_configuration.py | 251 +++++++++++++++++++ 3 files changed, 282 insertions(+), 7 deletions(-) diff --git a/edi_core_oca/models/edi_configuration.py b/edi_core_oca/models/edi_configuration.py index 795d141ce..9d08a308c 100644 --- a/edi_core_oca/models/edi_configuration.py +++ b/edi_core_oca/models/edi_configuration.py @@ -208,6 +208,35 @@ def edi_get_conf(self, trigger, backend=None): domain.append(("backend_id", "in", backend_ids)) return self.filtered_domain(domain) + @api.model + def edi_get_conf_global(self, exchange_record, trigger): + """Return active global configurations matching the given event. + + Unlike :meth:`edi_get_conf` -- which runs on a recordset of + configurations already linked to a partner -- global configurations + are not bound to any partner. We therefore have to derive the + filtering keys from the originating exchange record: + + * ``trigger`` must match the event code + * ``is_global`` must be True + * ``type_id`` must match the exchange type or be empty (applies to all) + * ``backend_id`` must match the backend or be empty (applies to all) + * ``model_name`` must match the related record model or be empty + (applies to all) + """ + related_model = exchange_record.model + model_options = [False] + if related_model: + model_options.append(related_model) + domain = [ + ("trigger", "=", trigger), + ("is_global", "=", True), + ("type_id", "in", [exchange_record.type_id.id, False]), + ("backend_id", "in", [exchange_record.backend_id.id, False]), + ("model_name", "in", model_options), + ] + return self.search(domain) + def action_view_partners(self): # TODO: add tests partner_model = self.env["res.partner"] diff --git a/edi_core_oca/models/edi_exchange_record.py b/edi_core_oca/models/edi_exchange_record.py index ee86c86d3..685a98bb0 100644 --- a/edi_core_oca/models/edi_exchange_record.py +++ b/edi_core_oca/models/edi_exchange_record.py @@ -535,14 +535,9 @@ def _notify_related_record(self, message, level="info"): def _trigger_edi_event(self, name, suffix=None, target=None, **kw): event_name = self._trigger_edi_event_make_name(name, suffix) target = target or self - - global_configs = self.env["edi.configuration"].search( - [ - ("trigger", "=", event_name), - ("is_global", "=", True), - ] + global_configs = self.env["edi.configuration"].edi_get_conf_global( + self, event_name ) - for conf in global_configs: conf.edi_exec_snippet_do(target, **kw) diff --git a/edi_core_oca/tests/test_edi_configuration.py b/edi_core_oca/tests/test_edi_configuration.py index 689a8f6c1..3e06cd944 100644 --- a/edi_core_oca/tests/test_edi_configuration.py +++ b/edi_core_oca/tests/test_edi_configuration.py @@ -152,3 +152,254 @@ def test_edi_code_snippet(self): ) # Check the new vals after execution self.assertEqual(vals, expected_value) + + +class TestEDIConfigurationGlobalEvents(EDIBackendCommonTestCase): + """Test the global event dispatch via edi.configuration. + + `EDIExchangeRecord._trigger_edi_event` looks up all `edi.configuration` + records flagged as `is_global` and matching the event trigger code, + then executes their `snippet_do` against the target record. + These tests verify the dispatch happens for all `notify_*` events + and that the proper target (exchange record vs related record) + is passed to the snippet. + """ + + # Snippet appends a marker per call so we can verify multiple invocations + # against different targets within the same transaction. + _marker_snippet = "conf.write({'description': (conf.description or '') + '|' + record._name})" + + @classmethod + def setUpClass(cls): + super().setUpClass() + vals = { + "model": cls.partner._name, + "res_id": cls.partner.id, + } + cls.record = cls.backend.create_record("test_csv_output", vals) + cls.trigger_model = cls.env["edi.configuration.trigger"] + cls.conf_model = cls.env["edi.configuration"] + # Reuse existing data triggers when available, create the missing ones. + cls.trigger_done = cls.env.ref("edi_core_oca.edi_config_trigger_record_done") + cls.trigger_error = cls.env.ref("edi_core_oca.edi_config_trigger_record_error") + cls.trigger_ack_received = cls._get_or_create_trigger( + "on_edi_exchange_done_ack_received", "On ACK received" + ) + cls.trigger_ack_missing = cls._get_or_create_trigger( + "on_edi_exchange_done_ack_missing", "On ACK missing" + ) + cls.trigger_ack_received_error = cls._get_or_create_trigger( + "on_edi_exchange_done_ack_received_error", "On ACK received error" + ) + cls.trigger_generate_complete = cls._get_or_create_trigger( + "on_edi_exchange_generate_complete", "On generate complete" + ) + + @classmethod + def _get_or_create_trigger(cls, code, name): + trigger = cls.trigger_model.search([("code", "=", code)], limit=1) + if not trigger: + trigger = cls.trigger_model.create({"name": name, "code": code}) + return trigger + + def _make_conf(self, trigger, name, is_global=True, snippet=None, **overrides): + vals = { + "name": name, + "active": True, + "backend_id": self.backend.id, + "type_id": self.exchange_type_out.id, + "trigger_id": trigger.id, + "is_global": is_global, + "snippet_do": snippet or self._marker_snippet, + } + vals.update(overrides) + return self.conf_model.create(vals) + + def test_notify_done_triggers_global_conf(self): + conf = self._make_conf(self.trigger_done, "Global Done") + self.record._notify_done() + self.assertEqual(conf.description, f"|{self.record._name}") + + def test_notify_error_triggers_global_conf(self): + conf = self._make_conf(self.trigger_error, "Global Error") + self.record._notify_error("send_ko") + self.assertEqual(conf.description, f"|{self.record._name}") + + def test_notify_ack_received_triggers_global_conf(self): + conf = self._make_conf(self.trigger_ack_received, "Global ACK received") + self.record._notify_ack_received() + self.assertEqual(conf.description, f"|{self.record._name}") + + def test_notify_ack_missing_triggers_global_conf(self): + conf = self._make_conf(self.trigger_ack_missing, "Global ACK missing") + self.record._notify_ack_missing() + self.assertEqual(conf.description, f"|{self.record._name}") + + def test_notify_ack_received_error_triggers_global_conf(self): + conf = self._make_conf( + self.trigger_ack_received_error, "Global ACK received error" + ) + self.record._notify_ack_received_error() + self.assertEqual(conf.description, f"|{self.record._name}") + + def test_non_global_conf_is_ignored(self): + conf = self._make_conf(self.trigger_done, "Non Global Done", is_global=False) + self.record._notify_done() + self.assertFalse(conf.description) + + def test_inactive_global_conf_is_ignored(self): + conf = self._make_conf(self.trigger_done, "Inactive Global Done") + conf.active = False + self.record._notify_done() + self.assertFalse(conf.description) + + def test_notify_action_complete_dispatches_to_both_targets(self): + """`notify_action_complete` fires the event twice when the related + record exists: once with the exchange record as target, once with the + related record (partner here).""" + conf = self._make_conf( + self.trigger_generate_complete, "Global generate complete" + ) + # Sanity check: the exchange record has a related record. + self.assertTrue(self.record.related_record_exists) + self.record.notify_action_complete("generate") + # The snippet appended one marker per call: exchange record then partner. + self.assertEqual( + conf.description, + f"|{self.record._name}|{self.partner._name}", + ) + + def test_notify_action_complete_no_related_record(self): + """When no related record exists, the event fires only on the + exchange record itself.""" + conf = self._make_conf( + self.trigger_generate_complete, "Global generate complete - no related" + ) + # Create an exchange record with no related record. + orphan_record = self.backend.create_record( + "test_csv_output", {"model": False, "res_id": False} + ) + orphan_record.notify_action_complete("generate") + self.assertEqual(conf.description, f"|{orphan_record._name}") + + def test_snippet_receives_conf_and_record(self): + """The snippet eval context must expose both `conf` (the configuration) + and `record` (the target of the event).""" + snippet = ( + "conf.write({'description': 'conf=%s|record=%s' % " + "(conf.name, record.display_name)})" + ) + conf = self._make_conf(self.trigger_done, "Context check", snippet=snippet) + self.record._notify_done() + self.assertEqual( + conf.description, + f"conf={conf.name}|record={self.record.display_name}", + ) + + def test_multiple_global_confs_all_executed(self): + """All global confs matching the trigger are executed.""" + conf1 = self._make_conf(self.trigger_done, "Global Done 1") + conf2 = self._make_conf(self.trigger_done, "Global Done 2") + self.record._notify_done() + self.assertEqual(conf1.description, f"|{self.record._name}") + self.assertEqual(conf2.description, f"|{self.record._name}") + + # ------------------------------------------------------------------ + # Filtering tests for `edi_get_conf_global` + # ------------------------------------------------------------------ + def test_filter_by_type_mismatch(self): + """A conf bound to a different exchange type must not fire.""" + conf = self._make_conf( + self.trigger_done, + "Wrong type", + type_id=self.exchange_type_in.id, + ) + self.record._notify_done() + self.assertFalse(conf.description) + + def test_filter_by_type_empty_matches(self): + """A conf without a type matches any exchange record's type.""" + conf = self._make_conf(self.trigger_done, "No type", type_id=False) + self.record._notify_done() + self.assertEqual(conf.description, f"|{self.record._name}") + + def test_filter_by_backend_mismatch(self): + """A conf bound to a different backend must not fire.""" + other_backend = self.env["edi.backend"].create( + { + "name": "Other backend", + "backend_type_id": self.backend.backend_type_id.id, + } + ) + # `_constrains_backend` requires backend to be compatible with the type's + # backend if the type has one set. Detach the type from the conf to test + # only the backend filter. + conf = self._make_conf( + self.trigger_done, + "Wrong backend", + backend_id=other_backend.id, + type_id=False, + ) + self.record._notify_done() + self.assertFalse(conf.description) + + def test_filter_by_backend_empty_matches(self): + """A conf without a backend matches any exchange record's backend.""" + conf = self._make_conf( + self.trigger_done, + "No backend", + backend_id=False, + type_id=False, + ) + self.record._notify_done() + self.assertEqual(conf.description, f"|{self.record._name}") + + def test_filter_by_model_mismatch(self): + """A conf bound to a different model must not fire.""" + other_model = self.env["ir.model"]._get("res.users") + conf = self._make_conf( + self.trigger_done, + "Wrong model", + model_id=other_model.id, + ) + self.record._notify_done() + self.assertFalse(conf.description) + + def test_filter_by_model_match(self): + """A conf bound to the related record model fires.""" + partner_model = self.env["ir.model"]._get(self.partner._name) + conf = self._make_conf( + self.trigger_done, + "Matching model", + model_id=partner_model.id, + ) + self.record._notify_done() + self.assertEqual(conf.description, f"|{self.record._name}") + + def test_filter_by_model_orphan_record(self): + """A conf with a model is skipped on records with no related model.""" + partner_model = self.env["ir.model"]._get(self.partner._name) + conf_with_model = self._make_conf( + self.trigger_done, + "Model bound", + model_id=partner_model.id, + ) + conf_no_model = self._make_conf(self.trigger_done, "Model-less") + orphan_record = self.backend.create_record( + "test_csv_output", {"model": False, "res_id": False} + ) + orphan_record._notify_done() + self.assertFalse(conf_with_model.description) + self.assertEqual(conf_no_model.description, f"|{orphan_record._name}") + + def test_edi_get_conf_global_returns_only_matching(self): + """Direct check on the new helper method.""" + matching = self._make_conf(self.trigger_done, "Matching") + wrong_trigger = self._make_conf(self.trigger_error, "Wrong trigger") + non_global = self._make_conf(self.trigger_done, "Non global", is_global=False) + result = self.env["edi.configuration"].edi_get_conf_global( + self.record, self.trigger_done.code + ) + self.assertIn(matching, result) + self.assertNotIn(wrong_trigger, result) + self.assertNotIn(non_global, result) From 9dc23550f3785df93515820e0b25aab445ad4ea0 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 11:38:31 +0200 Subject: [PATCH 06/42] [DOC] edi_core: add minimal docs for edi.conf --- edi_core_oca/readme/CONFIGURE.md | 95 ++++++++++++++++++++++++++++++ edi_core_oca/readme/DESCRIPTION.md | 9 ++- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/edi_core_oca/readme/CONFIGURE.md b/edi_core_oca/readme/CONFIGURE.md index 48045deba..a1957bbe0 100644 --- a/edi_core_oca/readme/CONFIGURE.md +++ b/edi_core_oca/readme/CONFIGURE.md @@ -63,3 +63,98 @@ backend to be used for the exchange. In case of "Custom" kind, you'll have to define your own logic to do something. + +## Custom event handlers via `edi.configuration` + +The framework can dispatch EDI lifecycle events to user-defined +configurations, providing a declarative alternative to component events. +Each `edi.configuration` record links a **trigger** (an +`edi.configuration.trigger` code) to a **snippet** (`snippet_do`) that is +executed every time the matching event fires on an exchange record. + +Built-in events fired by `EDIExchangeRecord` include: + +- `on_edi_exchange_done` — exchange processed successfully +- `on_edi_exchange_error` — exchange ended in error +- `on_edi_exchange_done_ack_received` — ACK file received +- `on_edi_exchange_done_ack_missing` — expected ACK not received +- `on_edi_exchange_done_ack_received_error` — ACK received with errors +- `on_edi_exchange__complete` — generic action completion (e.g. + `generate_complete`, `send_complete`), fired once on the exchange + record and once on its related record when present + +The snippet receives at least two variables in its evaluation context: + +- `conf` — the current `edi.configuration` record +- `record` — the target of the event (either the `edi.exchange.record` + itself or its related business record) + +Plus the standard `edi_exec_snippet_do` extras (`operation`, +`edi_action`, `old_value`, `vals`, ...). + +Two complementary lookup modes are available, and they can be combined +freely on the same flow. + +### Global event configurations + +Use this mode when you want a configuration to react to events on **any +business record** that travels through EDI, with no per-partner setup. + +Tick **Global Configuration** (`is_global`) on the `edi.configuration`. +When an event fires, the framework calls +`edi.configuration.edi_get_conf_global(exchange_record, trigger)` which +selects all active global configurations whose `trigger` matches the +event code, filtered by the originating exchange record: + +- **Exchange type** (`type_id`): must match the exchange record's type, + or be left empty to apply to every type +- **Backend** (`backend_id`): must match the exchange record's backend, + or be left empty to apply to every backend +- **Model** (`model_id` / `model_name`): must match the related record + model (e.g. `sale.order`, `account.move`), or be left empty to apply + to every model + +Empty values mean "applies to all". Inactive configurations and +non-global configurations are ignored. All matching configurations are +executed in sequence. + +Typical use cases: + +- Posting a generic chatter message on every exchange that ends in error +- Pushing a notification to an external system every time an ACK is + received for a given backend +- Logging extra audit information for every exchange of a given type + +### Partner-specific (relation-based) event configurations + +Use this mode when the reaction must depend on the partner (or any +other related record) involved in the exchange. + +In this case configurations are **not** marked as global. Instead, the +business record exposes an `edi_config_ids` relation (via +`edi.exchange.consumer.mixin._edi_config_field_relation`, which by +default returns `self.env["edi.configuration"]` and can be overridden, +for example to point at `self.partner_id.edi_config_ids`). When an +event fires on the business record (e.g. on create, on write, +on send-via-email/EDI), the framework calls +`edi_confs.edi_get_conf(trigger)` on that relation and runs the +matching snippets. + +Compared with global configurations: + +- **Discovery** comes from the record's own relation, not from a + database-wide search; this is the right place to model "this partner + wants this behaviour" rules +- **Filtering** is reduced to `trigger` and (optionally) `backend_id`, + since the recordset is already narrowed by the relation +- The same `snippet_do` API applies, so a snippet can be reused + verbatim between global and partner-specific configurations + +Typical use cases: + +- Sending a specific EDI flow only for a subset of partners +- Customising the document generation per customer (e.g. different + email template, different transport) +- Switching between EDI and email delivery based on partner + preferences + diff --git a/edi_core_oca/readme/DESCRIPTION.md b/edi_core_oca/readme/DESCRIPTION.md index 8cfcb472b..f9aee7085 100644 --- a/edi_core_oca/readme/DESCRIPTION.md +++ b/edi_core_oca/readme/DESCRIPTION.md @@ -8,4 +8,11 @@ Provides following models: 3. EDI Exchange Type, to define file types of exchange 4. EDI Exchange Record, to define a record exchanged between systems -Also define a mixin to be inherited by records that will generate EDIs +Also define a mixin to be inherited by records that will generate EDIs. + +In addition, the module ships an ``edi.configuration`` mechanism that lets +users react to EDI events declaratively, by writing small Python snippets +attached to event triggers. This can be used as a lightweight alternative +to component event listeners: configurations can react globally (on any +exchange) or be scoped to a specific partner (or any related record), +exchange type, backend and target model. See ``CONFIGURE.md`` for details. From abeda5ea3c3d3d01be032046ea9a505bb653839e Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 11:39:43 +0200 Subject: [PATCH 07/42] [DOC] edi_core: add readme/newsfragments --- edi_core_oca/readme/newsfragments/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 edi_core_oca/readme/newsfragments/.gitkeep diff --git a/edi_core_oca/readme/newsfragments/.gitkeep b/edi_core_oca/readme/newsfragments/.gitkeep new file mode 100644 index 000000000..e69de29bb From bbc55a344429755122a2229201510a2a9a04df7c Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 11:54:21 +0200 Subject: [PATCH 08/42] [DOC] edi_core: update newfragments --- .../global-edi-conf-events.feature | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 edi_core_oca/readme/newsfragments/global-edi-conf-events.feature diff --git a/edi_core_oca/readme/newsfragments/global-edi-conf-events.feature b/edi_core_oca/readme/newsfragments/global-edi-conf-events.feature new file mode 100644 index 000000000..d2678fe28 --- /dev/null +++ b/edi_core_oca/readme/newsfragments/global-edi-conf-events.feature @@ -0,0 +1,20 @@ +Introduce a new system for **global EDI events** based on ``edi.configuration`` +that can replace the use of component events. + +Any ``edi.configuration`` flagged as ``is_global`` is now picked up by +``EDIExchangeRecord._trigger_edi_event`` and its ``snippet_do`` is executed +whenever the matching event fires (``done``, ``error``, ``ack_received``, +``ack_missing``, ``ack_received_error``, ``_complete``, ...). + +Filtering is performed via the new ``edi.configuration.edi_get_conf_global`` +model method, which selects active global configurations matching the event +trigger code and, when set, the exchange type, the backend and the related +record model carried by the exchange record (empty values still mean "applies +to all"). This lets integrators subscribe to EDI events declaratively from +the UI instead of writing component listeners. + +Full test coverage is included for the dispatch on all ``notify_*`` events +(both on the exchange record and on the related record target) and for the +new filtering rules. + +Last but not lease: add minimal docs for edi.configuration. From 7ea42af10a5c6faec7efddb2e99e5e46317ed155 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 13:02:54 +0200 Subject: [PATCH 09/42] [FIX] edi_core: fix edi.configuration._constrains_backend No comparison if backend is not set or type is not set. --- edi_core_oca/models/edi_configuration.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/edi_core_oca/models/edi_configuration.py b/edi_core_oca/models/edi_configuration.py index 9d08a308c..82bade7d5 100644 --- a/edi_core_oca/models/edi_configuration.py +++ b/edi_core_oca/models/edi_configuration.py @@ -78,12 +78,14 @@ class EdiConfiguration(models.Model): @api.constrains("backend_id", "type_id") def _constrains_backend(self): for rec in self: + if not rec.backend_id: + continue if rec.type_id.backend_id: if rec.type_id.backend_id != rec.backend_id: raise exceptions.ValidationError( self.env._("Backend must match with exchange type's backend!") ) - else: + elif rec.type_id: if rec.type_id.backend_type_id != rec.backend_id.backend_type_id: raise exceptions.ValidationError( self.env._( From 40e51cf2942d78ed8dfab208fc3034b7beb61e9b Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 13:03:37 +0200 Subject: [PATCH 10/42] fixup! [IMP] edi_core: improve global edi.conf --- edi_core_oca/tests/test_edi_configuration.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/edi_core_oca/tests/test_edi_configuration.py b/edi_core_oca/tests/test_edi_configuration.py index 3e06cd944..d624b697e 100644 --- a/edi_core_oca/tests/test_edi_configuration.py +++ b/edi_core_oca/tests/test_edi_configuration.py @@ -167,7 +167,9 @@ class TestEDIConfigurationGlobalEvents(EDIBackendCommonTestCase): # Snippet appends a marker per call so we can verify multiple invocations # against different targets within the same transaction. - _marker_snippet = "conf.write({'description': (conf.description or '') + '|' + record._name})" + _marker_snippet = ( + "conf.write({'description': (conf.description or '') + '|' + record._name})" + ) @classmethod def setUpClass(cls): From c8669d55760d0d10d1cc3d2dae98106a0887aea9 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 19 May 2026 10:50:39 +0000 Subject: [PATCH 11/42] [BOT] post-merge updates --- README.md | 2 +- edi_core_oca/README.rst | 2 +- edi_core_oca/__manifest__.py | 2 +- edi_core_oca/static/description/index.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7eec74965..6c1d37f67 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ addon | version | maintainers | summary [edi_account_core_oca](edi_account_core_oca/) | 18.0.1.1.1 | etobella | Define EDI Configuration for Account Moves [edi_account_oca](edi_account_oca/) | 18.0.1.1.1 | etobella | Define some component listeners for Account Moves [edi_component_oca](edi_component_oca/) | 18.0.1.0.3 | simahawk etobella | Allow to use Connector as a source in EDI -[edi_core_oca](edi_core_oca/) | 18.0.1.6.6 | simahawk etobella | Define backends, exchange types, exchange records, basic automation and views for handling EDI exchanges. +[edi_core_oca](edi_core_oca/) | 18.0.1.6.7 | simahawk etobella | Define backends, exchange types, exchange records, basic automation and views for handling EDI exchanges. [edi_endpoint_oca](edi_endpoint_oca/) | 18.0.1.0.3 | | Base module allowing configuration of custom endpoints for EDI framework. [edi_exchange_template_oca](edi_exchange_template_oca/) | 18.0.1.3.3 | simahawk | Allows definition of exchanges via templates. [edi_exchange_template_party_data](edi_exchange_template_party_data/) | 18.0.1.0.1 | simahawk | Glue module between edi_exchange_template and edi_party_data diff --git a/edi_core_oca/README.rst b/edi_core_oca/README.rst index bb985bd0d..0843611f1 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:c609033733302fa71a3c01c11e2729fd2b47ccde0b9a1d0619bed03cc26db4fe + !! source digest: sha256:5c628265afabdb3f35660663f16f4a2f3723be87ea0a236fff1f36b19ab00947 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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 a2929a7ab..7b0b2fa87 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": "18.0.1.6.6", + "version": "18.0.1.6.7", "website": "https://github.com/OCA/edi-framework", "development_status": "Beta", "license": "LGPL-3", diff --git a/edi_core_oca/static/description/index.html b/edi_core_oca/static/description/index.html index 0273741fe..84618feea 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:c609033733302fa71a3c01c11e2729fd2b47ccde0b9a1d0619bed03cc26db4fe +!! source digest: sha256:5c628265afabdb3f35660663f16f4a2f3723be87ea0a236fff1f36b19ab00947 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

Base EDI backend.

From e5da5b608d24a7d11951a2e7985ac21f98ecf512 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 19 May 2026 12:52:36 +0200 Subject: [PATCH 12/42] edi_component: fix _trigger_edi_event to preserve event name The event name would be prefixed 2 times when calling super. --- edi_component_oca/models/edi_exchange_record.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/edi_component_oca/models/edi_exchange_record.py b/edi_component_oca/models/edi_exchange_record.py index c37c17f93..b6105c0d5 100644 --- a/edi_component_oca/models/edi_exchange_record.py +++ b/edi_component_oca/models/edi_exchange_record.py @@ -10,7 +10,7 @@ class EdiExchangeRecord(models.Model): def _trigger_edi_event(self, name, suffix=None, target=None, **kw): """Trigger a component event linked to this backend and edi exchange.""" - name = self._trigger_edi_event_make_name(name, suffix=suffix) + event_name = self._trigger_edi_event_make_name(name, suffix=suffix) target = target or self - target._event(name).notify(self, **kw) + target._event(event_name).notify(self, **kw) return super()._trigger_edi_event(name, suffix=suffix, target=target, **kw) From 08afd00537ae4280d18b71738aa29de8ec980742 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Wed, 20 May 2026 06:19:59 +0000 Subject: [PATCH 13/42] [UPD] Update edi_core_oca.pot --- edi_core_oca/i18n/edi_core_oca.pot | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/edi_core_oca/i18n/edi_core_oca.pot b/edi_core_oca/i18n/edi_core_oca.pot index 436bf77b7..35b6d0b94 100644 --- a/edi_core_oca/i18n/edi_core_oca.pot +++ b/edi_core_oca/i18n/edi_core_oca.pot @@ -1005,6 +1005,11 @@ msgstr "" msgid "Generator" msgstr "" +#. module: edi_core_oca +#: model:ir.model.fields,field_description:edi_core_oca.field_edi_configuration__is_global +msgid "Global Configuration" +msgstr "" + #. module: edi_core_oca #: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_exchange_record_view_search #: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_exchange_type_rule_view_search @@ -1069,6 +1074,13 @@ msgstr "" msgid "If checked, some messages have a delivery error." msgstr "" +#. module: edi_core_oca +#: model:ir.model.fields,help:edi_core_oca.field_edi_configuration__is_global +msgid "" +"If checked, this configuration will be executed for all records, regardless " +"of the partner relation." +msgstr "" + #. module: edi_core_oca #: model:ir.model.fields,help:edi_core_oca.field_edi_exchange_type__exchange_filename_sequence_id msgid "" From 5a34f6eaf93bde3daadb2a19d16a33d05d12b046 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Wed, 20 May 2026 06:20:01 +0000 Subject: [PATCH 14/42] [UPD] Update edi_oca.pot --- edi_oca/i18n/edi_oca.pot | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/edi_oca/i18n/edi_oca.pot b/edi_oca/i18n/edi_oca.pot index f3babc523..543fed99d 100644 --- a/edi_oca/i18n/edi_oca.pot +++ b/edi_oca/i18n/edi_oca.pot @@ -882,6 +882,11 @@ msgstr "" msgid "Generator" msgstr "" +#. module: edi_oca +#: model:ir.model.fields,field_description:edi_oca.field_edi_configuration__is_global +msgid "Global Configuration" +msgstr "" + #. module: edi_oca #: model_terms:ir.ui.view,arch_db:edi_oca.edi_exchange_record_view_search #: model_terms:ir.ui.view,arch_db:edi_oca.edi_exchange_type_rule_view_search @@ -946,6 +951,13 @@ msgstr "" msgid "If checked, some messages have a delivery error." msgstr "" +#. module: edi_oca +#: model:ir.model.fields,help:edi_oca.field_edi_configuration__is_global +msgid "" +"If checked, this configuration will be executed for all records, regardless " +"of the partner relation." +msgstr "" + #. module: edi_oca #: model:ir.model.fields,help:edi_oca.field_edi_exchange_type__exchange_filename_sequence_id msgid "" From 9e2a8275a38820660e29701a11d92010da6adfbb Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 20 May 2026 06:26:55 +0000 Subject: [PATCH 15/42] [BOT] post-merge updates --- README.md | 4 +- edi_component_oca/README.rst | 2 +- edi_component_oca/__manifest__.py | 2 +- .../static/description/index.html | 2 +- edi_core_oca/README.rst | 143 ++++++++++++- edi_core_oca/__manifest__.py | 2 +- edi_core_oca/readme/HISTORY.md | 24 +++ .../global-edi-conf-events.feature | 20 -- edi_core_oca/static/description/index.html | 189 +++++++++++++++--- 9 files changed, 336 insertions(+), 52 deletions(-) create mode 100644 edi_core_oca/readme/HISTORY.md delete mode 100644 edi_core_oca/readme/newsfragments/global-edi-conf-events.feature diff --git a/README.md b/README.md index 6c1d37f67..5e016a5ca 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ addon | version | maintainers | summary --- | --- | --- | --- [edi_account_core_oca](edi_account_core_oca/) | 18.0.1.1.1 | etobella | Define EDI Configuration for Account Moves [edi_account_oca](edi_account_oca/) | 18.0.1.1.1 | etobella | Define some component listeners for Account Moves -[edi_component_oca](edi_component_oca/) | 18.0.1.0.3 | simahawk etobella | Allow to use Connector as a source in EDI -[edi_core_oca](edi_core_oca/) | 18.0.1.6.7 | simahawk etobella | Define backends, exchange types, exchange records, basic automation and views for handling EDI exchanges. +[edi_component_oca](edi_component_oca/) | 18.0.1.1.0 | simahawk etobella | Allow to use Connector as a source in EDI +[edi_core_oca](edi_core_oca/) | 18.0.1.7.0 | simahawk etobella | Define backends, exchange types, exchange records, basic automation and views for handling EDI exchanges. [edi_endpoint_oca](edi_endpoint_oca/) | 18.0.1.0.3 | | Base module allowing configuration of custom endpoints for EDI framework. [edi_exchange_template_oca](edi_exchange_template_oca/) | 18.0.1.3.3 | simahawk | Allows definition of exchanges via templates. [edi_exchange_template_party_data](edi_exchange_template_party_data/) | 18.0.1.0.1 | simahawk | Glue module between edi_exchange_template and edi_party_data diff --git a/edi_component_oca/README.rst b/edi_component_oca/README.rst index db2cffdd7..06d07a3d0 100644 --- a/edi_component_oca/README.rst +++ b/edi_component_oca/README.rst @@ -11,7 +11,7 @@ Edi Connector Oca !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:81c11c0d670f363513d25e5d2d6cb038f1fc56580f20c837c2d2a7665798018d + !! source digest: sha256:6c5e69ae42fdaaaf428ee2b9d0a8e14ba1b5515b6fd5daa7ca926cb5800567b4 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/edi_component_oca/__manifest__.py b/edi_component_oca/__manifest__.py index fa8111718..c589696e7 100644 --- a/edi_component_oca/__manifest__.py +++ b/edi_component_oca/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Edi Connector Oca", "summary": """Allow to use Connector as a source in EDI""", - "version": "18.0.1.0.3", + "version": "18.0.1.1.0", "license": "LGPL-3", "author": "ACSONE,Dixmit,Camptocamp,Odoo Community Association (OCA)", "maintainers": ["simahawk", "etobella"], diff --git a/edi_component_oca/static/description/index.html b/edi_component_oca/static/description/index.html index e6128b6fa..f256369c0 100644 --- a/edi_component_oca/static/description/index.html +++ b/edi_component_oca/static/description/index.html @@ -372,7 +372,7 @@

Edi Connector Oca

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:81c11c0d670f363513d25e5d2d6cb038f1fc56580f20c837c2d2a7665798018d +!! source digest: sha256:6c5e69ae42fdaaaf428ee2b9d0a8e14ba1b5515b6fd5daa7ca926cb5800567b4 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

This module allows to use components to handle code to execute on EDI diff --git a/edi_core_oca/README.rst b/edi_core_oca/README.rst index 0843611f1..55bdc1e55 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:5c628265afabdb3f35660663f16f4a2f3723be87ea0a236fff1f36b19ab00947 + !! source digest: sha256:27258fb23153f2660be19d7c76b04c4d09d35d1e240b779076c7f4a9fa66d9f2 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -42,7 +42,15 @@ Provides following models: 3. EDI Exchange Type, to define file types of exchange 4. EDI Exchange Record, to define a record exchanged between systems -Also define a mixin to be inherited by records that will generate EDIs +Also define a mixin to be inherited by records that will generate EDIs. + +In addition, the module ships an ``edi.configuration`` mechanism that +lets users react to EDI events declaratively, by writing small Python +snippets attached to event triggers. This can be used as a lightweight +alternative to component event listeners: configurations can react +globally (on any exchange) or be scoped to a specific partner (or any +related record), exchange type, backend and target model. See +``CONFIGURE.md`` for details. **Table of contents** @@ -130,6 +138,104 @@ backend to be used for the exchange. In case of "Custom" kind, you'll have to define your own logic to do something. +Custom event handlers via ``edi.configuration`` +----------------------------------------------- + +The framework can dispatch EDI lifecycle events to user-defined +configurations, providing a declarative alternative to component events. +Each ``edi.configuration`` record links a **trigger** (an +``edi.configuration.trigger`` code) to a **snippet** (``snippet_do``) +that is executed every time the matching event fires on an exchange +record. + +Built-in events fired by ``EDIExchangeRecord`` include: + +- ``on_edi_exchange_done`` — exchange processed successfully +- ``on_edi_exchange_error`` — exchange ended in error +- ``on_edi_exchange_done_ack_received`` — ACK file received +- ``on_edi_exchange_done_ack_missing`` — expected ACK not received +- ``on_edi_exchange_done_ack_received_error`` — ACK received with errors +- ``on_edi_exchange__complete`` — generic action completion + (e.g. ``generate_complete``, ``send_complete``), fired once on the + exchange record and once on its related record when present + +The snippet receives at least two variables in its evaluation context: + +- ``conf`` — the current ``edi.configuration`` record +- ``record`` — the target of the event (either the + ``edi.exchange.record`` itself or its related business record) + +Plus the standard ``edi_exec_snippet_do`` extras (``operation``, +``edi_action``, ``old_value``, ``vals``, ...). + +Two complementary lookup modes are available, and they can be combined +freely on the same flow. + +Global event configurations +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use this mode when you want a configuration to react to events on **any +business record** that travels through EDI, with no per-partner setup. + +Tick **Global Configuration** (``is_global``) on the +``edi.configuration``. When an event fires, the framework calls +``edi.configuration.edi_get_conf_global(exchange_record, trigger)`` +which selects all active global configurations whose ``trigger`` matches +the event code, filtered by the originating exchange record: + +- **Exchange type** (``type_id``): must match the exchange record's + type, or be left empty to apply to every type +- **Backend** (``backend_id``): must match the exchange record's + backend, or be left empty to apply to every backend +- **Model** (``model_id`` / ``model_name``): must match the related + record model (e.g. ``sale.order``, ``account.move``), or be left empty + to apply to every model + +Empty values mean "applies to all". Inactive configurations and +non-global configurations are ignored. All matching configurations are +executed in sequence. + +Typical use cases: + +- Posting a generic chatter message on every exchange that ends in error +- Pushing a notification to an external system every time an ACK is + received for a given backend +- Logging extra audit information for every exchange of a given type + +Partner-specific (relation-based) event configurations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use this mode when the reaction must depend on the partner (or any other +related record) involved in the exchange. + +In this case configurations are **not** marked as global. Instead, the +business record exposes an ``edi_config_ids`` relation (via +``edi.exchange.consumer.mixin._edi_config_field_relation``, which by +default returns ``self.env["edi.configuration"]`` and can be overridden, +for example to point at ``self.partner_id.edi_config_ids``). When an +event fires on the business record (e.g. on create, on write, on +send-via-email/EDI), the framework calls +``edi_confs.edi_get_conf(trigger)`` on that relation and runs the +matching snippets. + +Compared with global configurations: + +- **Discovery** comes from the record's own relation, not from a + database-wide search; this is the right place to model "this partner + wants this behaviour" rules +- **Filtering** is reduced to ``trigger`` and (optionally) + ``backend_id``, since the recordset is already narrowed by the + relation +- The same ``snippet_do`` API applies, so a snippet can be reused + verbatim between global and partner-specific configurations + +Typical use cases: + +- Sending a specific EDI flow only for a subset of partners +- Customising the document generation per customer (e.g. different email + template, different transport) +- Switching between EDI and email delivery based on partner preferences + Usage ===== @@ -182,6 +288,39 @@ Components dependancy has been removed and set on a new dependant module ``edi_component_oca``. Module ``edi_oca`` has been_renamed to ``edi_core_oca``. +Changelog +========= + +18.0.1.7.0 (2026-05-20) +----------------------- + +Features +~~~~~~~~ + +- Introduce a new system for **global EDI events** based on + ``edi.configuration`` that can replace the use of component events. + + Any ``edi.configuration`` flagged as ``is_global`` is now picked up by + ``EDIExchangeRecord._trigger_edi_event`` and its ``snippet_do`` is + executed whenever the matching event fires (``done``, ``error``, + ``ack_received``, ``ack_missing``, ``ack_received_error``, + ``_complete``, ...). + + Filtering is performed via the new + ``edi.configuration.edi_get_conf_global`` model method, which selects + active global configurations matching the event trigger code and, when + set, the exchange type, the backend and the related record model + carried by the exchange record (empty values still mean "applies to + all"). This lets integrators subscribe to EDI events declaratively + from the UI instead of writing component listeners. + + Full test coverage is included for the dispatch on all ``notify_*`` + events (both on the exchange record and on the related record target) + and for the new filtering rules. + + Last but not lease: add minimal docs for edi.configuration. + (`#global-edi-conf-events `__) + Bug Tracker =========== diff --git a/edi_core_oca/__manifest__.py b/edi_core_oca/__manifest__.py index 7b0b2fa87..c3a09f07b 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": "18.0.1.6.7", + "version": "18.0.1.7.0", "website": "https://github.com/OCA/edi-framework", "development_status": "Beta", "license": "LGPL-3", diff --git a/edi_core_oca/readme/HISTORY.md b/edi_core_oca/readme/HISTORY.md new file mode 100644 index 000000000..755aa4d94 --- /dev/null +++ b/edi_core_oca/readme/HISTORY.md @@ -0,0 +1,24 @@ +## 18.0.1.7.0 (2026-05-20) + +### Features + +- Introduce a new system for **global EDI events** based on ``edi.configuration`` + that can replace the use of component events. + + Any ``edi.configuration`` flagged as ``is_global`` is now picked up by + ``EDIExchangeRecord._trigger_edi_event`` and its ``snippet_do`` is executed + whenever the matching event fires (``done``, ``error``, ``ack_received``, + ``ack_missing``, ``ack_received_error``, ``_complete``, ...). + + Filtering is performed via the new ``edi.configuration.edi_get_conf_global`` + model method, which selects active global configurations matching the event + trigger code and, when set, the exchange type, the backend and the related + record model carried by the exchange record (empty values still mean "applies + to all"). This lets integrators subscribe to EDI events declaratively from + the UI instead of writing component listeners. + + Full test coverage is included for the dispatch on all ``notify_*`` events + (both on the exchange record and on the related record target) and for the + new filtering rules. + + Last but not lease: add minimal docs for edi.configuration. ([#global-edi-conf-events](https://github.com/OCA/edi-framework/issues/global-edi-conf-events)) diff --git a/edi_core_oca/readme/newsfragments/global-edi-conf-events.feature b/edi_core_oca/readme/newsfragments/global-edi-conf-events.feature deleted file mode 100644 index d2678fe28..000000000 --- a/edi_core_oca/readme/newsfragments/global-edi-conf-events.feature +++ /dev/null @@ -1,20 +0,0 @@ -Introduce a new system for **global EDI events** based on ``edi.configuration`` -that can replace the use of component events. - -Any ``edi.configuration`` flagged as ``is_global`` is now picked up by -``EDIExchangeRecord._trigger_edi_event`` and its ``snippet_do`` is executed -whenever the matching event fires (``done``, ``error``, ``ack_received``, -``ack_missing``, ``ack_received_error``, ``_complete``, ...). - -Filtering is performed via the new ``edi.configuration.edi_get_conf_global`` -model method, which selects active global configurations matching the event -trigger code and, when set, the exchange type, the backend and the related -record model carried by the exchange record (empty values still mean "applies -to all"). This lets integrators subscribe to EDI events declaratively from -the UI instead of writing component listeners. - -Full test coverage is included for the dispatch on all ``notify_*`` events -(both on the exchange record and on the related record target) and for the -new filtering rules. - -Last but not lease: add minimal docs for edi.configuration. diff --git a/edi_core_oca/static/description/index.html b/edi_core_oca/static/description/index.html index 84618feea..de6475a29 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:5c628265afabdb3f35660663f16f4a2f3723be87ea0a236fff1f36b19ab00947 +!! source digest: sha256:27258fb23153f2660be19d7c76b04c4d09d35d1e240b779076c7f4a9fa66d9f2 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

Base EDI backend.

@@ -384,7 +384,14 @@

EDI

  • EDI Exchange Type, to define file types of exchange
  • EDI Exchange Record, to define a record exchanged between systems
  • -

    Also define a mixin to be inherited by records that will generate EDIs

    +

    Also define a mixin to be inherited by records that will generate EDIs.

    +

    In addition, the module ships an edi.configuration mechanism that +lets users react to EDI events declaratively, by writing small Python +snippets attached to event triggers. This can be used as a lightweight +alternative to component event listeners: configurations can react +globally (on any exchange) or be scoped to a specific partner (or any +related record), exchange type, backend and target model. See +CONFIGURE.md for details.

    Table of contents

    +
    +

    Custom event handlers via edi.configuration

    +

    The framework can dispatch EDI lifecycle events to user-defined +configurations, providing a declarative alternative to component events. +Each edi.configuration record links a trigger (an +edi.configuration.trigger code) to a snippet (snippet_do) +that is executed every time the matching event fires on an exchange +record.

    +

    Built-in events fired by EDIExchangeRecord include:

    +
      +
    • on_edi_exchange_done — exchange processed successfully
    • +
    • on_edi_exchange_error — exchange ended in error
    • +
    • on_edi_exchange_done_ack_received — ACK file received
    • +
    • on_edi_exchange_done_ack_missing — expected ACK not received
    • +
    • on_edi_exchange_done_ack_received_error — ACK received with errors
    • +
    • on_edi_exchange_<action>_complete — generic action completion +(e.g. generate_complete, send_complete), fired once on the +exchange record and once on its related record when present
    • +
    +

    The snippet receives at least two variables in its evaluation context:

    +
      +
    • conf — the current edi.configuration record
    • +
    • record — the target of the event (either the +edi.exchange.record itself or its related business record)
    • +
    +

    Plus the standard edi_exec_snippet_do extras (operation, +edi_action, old_value, vals, …).

    +

    Two complementary lookup modes are available, and they can be combined +freely on the same flow.

    +
    +

    Global event configurations

    +

    Use this mode when you want a configuration to react to events on any +business record that travels through EDI, with no per-partner setup.

    +

    Tick Global Configuration (is_global) on the +edi.configuration. When an event fires, the framework calls +edi.configuration.edi_get_conf_global(exchange_record, trigger) +which selects all active global configurations whose trigger matches +the event code, filtered by the originating exchange record:

    +
      +
    • Exchange type (type_id): must match the exchange record’s +type, or be left empty to apply to every type
    • +
    • Backend (backend_id): must match the exchange record’s +backend, or be left empty to apply to every backend
    • +
    • Model (model_id / model_name): must match the related +record model (e.g. sale.order, account.move), or be left empty +to apply to every model
    • +
    +

    Empty values mean “applies to all”. Inactive configurations and +non-global configurations are ignored. All matching configurations are +executed in sequence.

    +

    Typical use cases:

    +
      +
    • Posting a generic chatter message on every exchange that ends in error
    • +
    • Pushing a notification to an external system every time an ACK is +received for a given backend
    • +
    • Logging extra audit information for every exchange of a given type
    • +
    +
    +
    +

    Partner-specific (relation-based) event configurations

    +

    Use this mode when the reaction must depend on the partner (or any other +related record) involved in the exchange.

    +

    In this case configurations are not marked as global. Instead, the +business record exposes an edi_config_ids relation (via +edi.exchange.consumer.mixin._edi_config_field_relation, which by +default returns self.env["edi.configuration"] and can be overridden, +for example to point at self.partner_id.edi_config_ids). When an +event fires on the business record (e.g. on create, on write, on +send-via-email/EDI), the framework calls +edi_confs.edi_get_conf(trigger) on that relation and runs the +matching snippets.

    +

    Compared with global configurations:

    +
      +
    • Discovery comes from the record’s own relation, not from a +database-wide search; this is the right place to model “this partner +wants this behaviour” rules
    • +
    • Filtering is reduced to trigger and (optionally) +backend_id, since the recordset is already narrowed by the +relation
    • +
    • The same snippet_do API applies, so a snippet can be reused +verbatim between global and partner-specific configurations
    • +
    +

    Typical use cases:

    +
      +
    • Sending a specific EDI flow only for a subset of partners
    • +
    • Customising the document generation per customer (e.g. different email +template, different transport)
    • +
    • Switching between EDI and email delivery based on partner preferences
    • +
    +
    +
    -

    Usage

    +

    Usage

    After certain operations or manual execution, Exchange records will be generated. This Exchange records might be input records or outputs records.

    The change of state can be manually executed by the system or be managed through by ir.cron.

    -

    Output Exchange records

    +

    Output Exchange records

    An output record is intended to be used for exchange information from Odoo to another system.

    The flow of an output record should be:

    @@ -509,7 +619,7 @@

    Output Exchange records

    -

    Input Exchange records

    +

    Input Exchange records

    An input record is intended to be used for exchange information another system to odoo.

    The flow of an input record should be:

    @@ -522,20 +632,51 @@

    Input Exchange records

    -

    Known issues / Roadmap

    +

    Known issues / Roadmap

    -

    14.0.1.0.0

    +

    14.0.1.0.0

    The module name has been changed from edi to edi_oca.

    -

    18.0.1.4.0

    +

    18.0.1.4.0

    Components dependancy has been removed and set on a new dependant module edi_component_oca. Module edi_oca has been_renamed to edi_core_oca.

    +
    +

    Changelog

    +
    +

    18.0.1.7.0 (2026-05-20)

    +
    +

    Features

    +
      +
    • Introduce a new system for global EDI events based on +edi.configuration that can replace the use of component events.

      +

      Any edi.configuration flagged as is_global is now picked up by +EDIExchangeRecord._trigger_edi_event and its snippet_do is +executed whenever the matching event fires (done, error, +ack_received, ack_missing, ack_received_error, +<action>_complete, …).

      +

      Filtering is performed via the new +edi.configuration.edi_get_conf_global model method, which selects +active global configurations matching the event trigger code and, when +set, the exchange type, the backend and the related record model +carried by the exchange record (empty values still mean “applies to +all”). This lets integrators subscribe to EDI events declaratively +from the UI instead of writing component listeners.

      +

      Full test coverage is included for the dispatch on all notify_* +events (both on the exchange record and on the related record target) +and for the new filtering rules.

      +

      Last but not lease: add minimal docs for edi.configuration. +(#global-edi-conf-events)

      +
    • +
    +
    +
    +
    -

    Bug Tracker

    +

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -543,9 +684,9 @@

    Bug Tracker

    Do not contact contributors directly about support or help with technical issues.

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • ACSONE
    • Dixmit
    • @@ -553,7 +694,7 @@

      Authors

    -

    Contributors

    +

    Contributors

    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association From ca8366437aed11916b1f84757dc100ea97e47efd Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 20 May 2026 06:27:09 +0000 Subject: [PATCH 16/42] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: edi-framework-18.0/edi-framework-18.0-edi_oca Translate-URL: https://translation.odoo-community.org/projects/edi-framework-18-0/edi-framework-18-0-edi_oca/ --- edi_oca/i18n/it.po | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/edi_oca/i18n/it.po b/edi_oca/i18n/it.po index 33faed5c2..d2cdce3ed 100644 --- a/edi_oca/i18n/it.po +++ b/edi_oca/i18n/it.po @@ -970,6 +970,11 @@ msgstr "Genera record di scambio" msgid "Generator" msgstr "Generatore" +#. module: edi_oca +#: model:ir.model.fields,field_description:edi_oca.field_edi_configuration__is_global +msgid "Global Configuration" +msgstr "" + #. module: edi_oca #: model_terms:ir.ui.view,arch_db:edi_oca.edi_exchange_record_view_search #: model_terms:ir.ui.view,arch_db:edi_oca.edi_exchange_type_rule_view_search @@ -1041,6 +1046,13 @@ msgstr "Se selezionata, nuovi messaggi richiedono attenzione." msgid "If checked, some messages have a delivery error." msgstr "Se selezionata, alcuni messaggi hanno un errore di consegna." +#. module: edi_oca +#: model:ir.model.fields,help:edi_oca.field_edi_configuration__is_global +msgid "" +"If checked, this configuration will be executed for all records, regardless " +"of the partner relation." +msgstr "" + #. module: edi_oca #: model:ir.model.fields,help:edi_oca.field_edi_exchange_type__exchange_filename_sequence_id msgid "" From f34cddb02472282b30e1d723a7ff12255ca414be Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 20 May 2026 06:27:09 +0000 Subject: [PATCH 17/42] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: edi-framework-18.0/edi-framework-18.0-edi_core_oca Translate-URL: https://translation.odoo-community.org/projects/edi-framework-18-0/edi-framework-18-0-edi_core_oca/ --- edi_core_oca/i18n/es.po | 12 ++++++++++++ edi_core_oca/i18n/fr.po | 12 ++++++++++++ edi_core_oca/i18n/it.po | 12 ++++++++++++ 3 files changed, 36 insertions(+) diff --git a/edi_core_oca/i18n/es.po b/edi_core_oca/i18n/es.po index 6aa91ccbc..ae4534243 100644 --- a/edi_core_oca/i18n/es.po +++ b/edi_core_oca/i18n/es.po @@ -1014,6 +1014,11 @@ msgstr "" msgid "Generator" msgstr "" +#. module: edi_core_oca +#: model:ir.model.fields,field_description:edi_core_oca.field_edi_configuration__is_global +msgid "Global Configuration" +msgstr "" + #. module: edi_core_oca #: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_exchange_record_view_search #: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_exchange_type_rule_view_search @@ -1078,6 +1083,13 @@ msgstr "" msgid "If checked, some messages have a delivery error." msgstr "" +#. module: edi_core_oca +#: model:ir.model.fields,help:edi_core_oca.field_edi_configuration__is_global +msgid "" +"If checked, this configuration will be executed for all records, regardless " +"of the partner relation." +msgstr "" + #. module: edi_core_oca #: model:ir.model.fields,help:edi_core_oca.field_edi_exchange_type__exchange_filename_sequence_id msgid "" diff --git a/edi_core_oca/i18n/fr.po b/edi_core_oca/i18n/fr.po index 28a967be4..b09c20623 100644 --- a/edi_core_oca/i18n/fr.po +++ b/edi_core_oca/i18n/fr.po @@ -1026,6 +1026,11 @@ msgstr "" msgid "Generator" msgstr "Générer" +#. module: edi_core_oca +#: model:ir.model.fields,field_description:edi_core_oca.field_edi_configuration__is_global +msgid "Global Configuration" +msgstr "" + #. module: edi_core_oca #: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_exchange_record_view_search #: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_exchange_type_rule_view_search @@ -1092,6 +1097,13 @@ msgstr "" msgid "If checked, some messages have a delivery error." msgstr "" +#. module: edi_core_oca +#: model:ir.model.fields,help:edi_core_oca.field_edi_configuration__is_global +msgid "" +"If checked, this configuration will be executed for all records, regardless " +"of the partner relation." +msgstr "" + #. module: edi_core_oca #: model:ir.model.fields,help:edi_core_oca.field_edi_exchange_type__exchange_filename_sequence_id msgid "" diff --git a/edi_core_oca/i18n/it.po b/edi_core_oca/i18n/it.po index 14ad64c81..737c2dedc 100644 --- a/edi_core_oca/i18n/it.po +++ b/edi_core_oca/i18n/it.po @@ -1099,6 +1099,11 @@ msgstr "Genera record di scambio" msgid "Generator" msgstr "Generatore" +#. module: edi_core_oca +#: model:ir.model.fields,field_description:edi_core_oca.field_edi_configuration__is_global +msgid "Global Configuration" +msgstr "" + #. module: edi_core_oca #: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_exchange_record_view_search #: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_exchange_type_rule_view_search @@ -1170,6 +1175,13 @@ msgstr "Se selezionata, nuovi messaggi richiedono attenzione." msgid "If checked, some messages have a delivery error." msgstr "Se selezionata, alcuni messaggi hanno un errore di consegna." +#. module: edi_core_oca +#: model:ir.model.fields,help:edi_core_oca.field_edi_configuration__is_global +msgid "" +"If checked, this configuration will be executed for all records, regardless " +"of the partner relation." +msgstr "" + #. module: edi_core_oca #: model:ir.model.fields,help:edi_core_oca.field_edi_exchange_type__exchange_filename_sequence_id msgid "" From 91ec8c62ca7eebd355ec9e353e35eef216c63edb Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Mon, 14 Feb 2022 16:41:16 +0100 Subject: [PATCH 18/42] [ADD] edi_purchase_oca [UPD] Update edi_purchase_oca.pot [UPD] README.rst --- edi_purchase_oca/README.rst | 86 ++++ edi_purchase_oca/__init__.py | 1 + edi_purchase_oca/__manifest__.py | 15 + edi_purchase_oca/i18n/edi_purchase_oca.pot | 39 ++ edi_purchase_oca/models/__init__.py | 1 + edi_purchase_oca/models/purchase_order.py | 21 + edi_purchase_oca/readme/CONTRIBUTORS.rst | 1 + edi_purchase_oca/readme/DESCRIPTION.rst | 14 + edi_purchase_oca/static/description/icon.png | Bin 0 -> 5055 bytes .../static/description/index.html | 430 ++++++++++++++++++ .../views/edi_exchange_record_views.xml | 30 ++ .../views/purchase_order_views.xml | 28 ++ 12 files changed, 666 insertions(+) create mode 100644 edi_purchase_oca/README.rst create mode 100644 edi_purchase_oca/__init__.py create mode 100644 edi_purchase_oca/__manifest__.py create mode 100644 edi_purchase_oca/i18n/edi_purchase_oca.pot create mode 100644 edi_purchase_oca/models/__init__.py create mode 100644 edi_purchase_oca/models/purchase_order.py create mode 100644 edi_purchase_oca/readme/CONTRIBUTORS.rst create mode 100644 edi_purchase_oca/readme/DESCRIPTION.rst create mode 100644 edi_purchase_oca/static/description/icon.png create mode 100644 edi_purchase_oca/static/description/index.html create mode 100644 edi_purchase_oca/views/edi_exchange_record_views.xml create mode 100644 edi_purchase_oca/views/purchase_order_views.xml diff --git a/edi_purchase_oca/README.rst b/edi_purchase_oca/README.rst new file mode 100644 index 000000000..d54839902 --- /dev/null +++ b/edi_purchase_oca/README.rst @@ -0,0 +1,86 @@ +============ +EDI Purchase +============ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github + :target: https://github.com/OCA/edi/tree/13.0/edi_purchase_oca + :alt: OCA/edi +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/edi-13-0/edi-13-0-edi_purchase_oca + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/226/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module intends to create a base to be extended by local edi rules +for purchase. + +In order to add a new integration, you need to create a listener: + +.. code-block:: python + + class MyEventListener(Component): + _name = "purchase.order.event.listener.demo" + _inherit = "base.event.listener" + _apply_on = ["purchase.order"] + + def on_button_confirm_purchase_order(self, move): + """Add your code here""" + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* Lois Rilo + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/edi `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/edi_purchase_oca/__init__.py b/edi_purchase_oca/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/edi_purchase_oca/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/edi_purchase_oca/__manifest__.py b/edi_purchase_oca/__manifest__.py new file mode 100644 index 000000000..c9ff3c686 --- /dev/null +++ b/edi_purchase_oca/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +{ + "name": "EDI Purchase", + "summary": """ + Define EDI Configuration for Purchase Orders""", + "version": "13.0.1.0.0", + "license": "LGPL-3", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/edi", + "depends": ["purchase", "edi", "component_event"], + "data": ["views/purchase_order_views.xml", "views/edi_exchange_record_views.xml"], + "demo": [], +} diff --git a/edi_purchase_oca/i18n/edi_purchase_oca.pot b/edi_purchase_oca/i18n/edi_purchase_oca.pot new file mode 100644 index 000000000..872766122 --- /dev/null +++ b/edi_purchase_oca/i18n/edi_purchase_oca.pot @@ -0,0 +1,39 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * edi_purchase_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: edi_purchase_oca +#: model_terms:ir.ui.view,arch_db:edi_purchase_oca.purchase_order_form +msgid "EDI" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.ui.menu,name:edi_purchase_oca.menu_purchase_edi_root +msgid "Exchange records" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model,name:edi_purchase_oca.model_purchase_order +msgid "Purchase Order" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.actions.act_window,name:edi_purchase_oca.act_open_edi_exchange_record_purchase_order_view +msgid "Purchase Order Exchange Record" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.ui.menu,name:edi_purchase_oca.menu_purchase_edi_exchange_record +msgid "Purchase Orders" +msgstr "" diff --git a/edi_purchase_oca/models/__init__.py b/edi_purchase_oca/models/__init__.py new file mode 100644 index 000000000..9f0353064 --- /dev/null +++ b/edi_purchase_oca/models/__init__.py @@ -0,0 +1 @@ +from . import purchase_order diff --git a/edi_purchase_oca/models/purchase_order.py b/edi_purchase_oca/models/purchase_order.py new file mode 100644 index 000000000..6ac69723e --- /dev/null +++ b/edi_purchase_oca/models/purchase_order.py @@ -0,0 +1,21 @@ +# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import models + + +class PurchaseOrder(models.Model): + _name = "purchase.order" + _inherit = ["purchase.order", "edi.exchange.consumer.mixin"] + + def button_confirm(self): + result = super().button_confirm() + if self: + self._event("on_button_confirm_purchase_order").notify(self) + return result + + def button_cancel(self): + result = super().button_cancel() + if self: + self._event("on_button_cancel_purchase_order").notify(self) + return result diff --git a/edi_purchase_oca/readme/CONTRIBUTORS.rst b/edi_purchase_oca/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..77dfbe89e --- /dev/null +++ b/edi_purchase_oca/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Lois Rilo diff --git a/edi_purchase_oca/readme/DESCRIPTION.rst b/edi_purchase_oca/readme/DESCRIPTION.rst new file mode 100644 index 000000000..9c5b5932c --- /dev/null +++ b/edi_purchase_oca/readme/DESCRIPTION.rst @@ -0,0 +1,14 @@ +This module intends to create a base to be extended by local edi rules +for purchase. + +In order to add a new integration, you need to create a listener: + +.. code-block:: python + + class MyEventListener(Component): + _name = "purchase.order.event.listener.demo" + _inherit = "base.event.listener" + _apply_on = ["purchase.order"] + + def on_button_confirm_purchase_order(self, move): + """Add your code here""" diff --git a/edi_purchase_oca/static/description/icon.png b/edi_purchase_oca/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a79752645ce327baadb6aba260751a044a5e4a8a GIT binary patch literal 5055 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 literal 0 HcmV?d00001 diff --git a/edi_purchase_oca/static/description/index.html b/edi_purchase_oca/static/description/index.html new file mode 100644 index 000000000..b7baa690b --- /dev/null +++ b/edi_purchase_oca/static/description/index.html @@ -0,0 +1,430 @@ + + + + + + +EDI Purchase + + + +
    +

    EDI Purchase

    + + +

    Beta License: LGPL-3 OCA/edi Translate me on Weblate Try me on Runbot

    +

    This module intends to create a base to be extended by local edi rules +for purchase.

    +

    In order to add a new integration, you need to create a listener:

    +
    +class MyEventListener(Component):
    +    _name = "purchase.order.event.listener.demo"
    +    _inherit = "base.event.listener"
    +    _apply_on = ["purchase.order"]
    +
    +    def on_button_confirm_purchase_order(self, move):
    +        """Add your code here"""
    +
    +

    Table of contents

    + +
    +

    Bug Tracker

    +

    Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

    +

    Do not contact contributors directly about support or help with technical issues.

    +
    +
    +

    Credits

    +
    +

    Authors

    +
      +
    • ForgeFlow
    • +
    +
    + +
    +

    Maintainers

    +

    This module is maintained by the OCA.

    +Odoo Community Association +

    OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

    +

    This module is part of the OCA/edi project on GitHub.

    +

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    +
    +
    +
    + + diff --git a/edi_purchase_oca/views/edi_exchange_record_views.xml b/edi_purchase_oca/views/edi_exchange_record_views.xml new file mode 100644 index 000000000..0413585df --- /dev/null +++ b/edi_purchase_oca/views/edi_exchange_record_views.xml @@ -0,0 +1,30 @@ + + + + + + Purchase Order Exchange Record + ir.actions.act_window + edi.exchange.record + tree,form + [('model', '=', 'purchase.order')] + {} + + + + diff --git a/edi_purchase_oca/views/purchase_order_views.xml b/edi_purchase_oca/views/purchase_order_views.xml new file mode 100644 index 000000000..6e17a3a3e --- /dev/null +++ b/edi_purchase_oca/views/purchase_order_views.xml @@ -0,0 +1,28 @@ + + + + + purchase.order.form - edi_purchase_oca + purchase.order + + + + + + + + From 745046f547008e84af8941b14a2e36ba86fa6c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Wed, 6 Apr 2022 12:13:58 +0200 Subject: [PATCH 19/42] [MIG] edi_purchase_oca: Migration to 14.0 [UPD] Update edi_purchase_oca.pot [UPD] README.rst [UPD] Update edi_purchase_oca.pot [UPD] Update edi_purchase_oca.pot [UPD] Update edi_purchase_oca.pot [UPD] README.rst --- edi_purchase_oca/README.rst | 23 +++--- edi_purchase_oca/__manifest__.py | 4 +- edi_purchase_oca/i18n/edi_purchase_oca.pot | 72 ++++++++++++++++++- .../static/description/index.html | 50 ++++++------- 4 files changed, 112 insertions(+), 37 deletions(-) diff --git a/edi_purchase_oca/README.rst b/edi_purchase_oca/README.rst index d54839902..0c30581b7 100644 --- a/edi_purchase_oca/README.rst +++ b/edi_purchase_oca/README.rst @@ -2,10 +2,13 @@ EDI Purchase ============ -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:8ab40ab13f4a26ea0ba04ff19e53af88e490d1b2e783699454ae6255422e00c3 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -14,16 +17,16 @@ EDI Purchase :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github - :target: https://github.com/OCA/edi/tree/13.0/edi_purchase_oca + :target: https://github.com/OCA/edi/tree/14.0/edi_purchase_oca :alt: OCA/edi .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/edi-13-0/edi-13-0-edi_purchase_oca + :target: https://translation.odoo-community.org/projects/edi-14-0/edi-14-0-edi_purchase_oca :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/226/13.0 - :alt: Try me on Runbot +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/edi&target_branch=14.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This module intends to create a base to be extended by local edi rules for purchase. @@ -50,8 +53,8 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us smashing it by providing a detailed and welcomed -`feedback `_. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -81,6 +84,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/edi `_ project on GitHub. +This module is part of the `OCA/edi `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/edi_purchase_oca/__manifest__.py b/edi_purchase_oca/__manifest__.py index c9ff3c686..be446cd40 100644 --- a/edi_purchase_oca/__manifest__.py +++ b/edi_purchase_oca/__manifest__.py @@ -5,11 +5,11 @@ "name": "EDI Purchase", "summary": """ Define EDI Configuration for Purchase Orders""", - "version": "13.0.1.0.0", + "version": "14.0.1.0.0", "license": "LGPL-3", "author": "ForgeFlow, Odoo Community Association (OCA)", "website": "https://github.com/OCA/edi", - "depends": ["purchase", "edi", "component_event"], + "depends": ["purchase", "edi_oca", "component_event"], "data": ["views/purchase_order_views.xml", "views/edi_exchange_record_views.xml"], "demo": [], } diff --git a/edi_purchase_oca/i18n/edi_purchase_oca.pot b/edi_purchase_oca/i18n/edi_purchase_oca.pot index 872766122..3cf50cd17 100644 --- a/edi_purchase_oca/i18n/edi_purchase_oca.pot +++ b/edi_purchase_oca/i18n/edi_purchase_oca.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 13.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -18,11 +18,71 @@ msgstr "" msgid "EDI" msgstr "" +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__disable_edi_auto +msgid "Disable auto" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__display_name +msgid "Display Name" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__origin_edi_endpoint_id +msgid "EDI origin endpoint" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__origin_exchange_type_id +msgid "EDI origin exchange type" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__origin_exchange_record_id +msgid "EDI origin record" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order__origin_exchange_record_id +msgid "EDI record that originated this document." +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__edi_config +msgid "Edi Config" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__edi_has_form_config +msgid "Edi Has Form Config" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__exchange_record_ids +msgid "Exchange Record" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__exchange_record_count +msgid "Exchange Record Count" +msgstr "" + #. module: edi_purchase_oca #: model:ir.ui.menu,name:edi_purchase_oca.menu_purchase_edi_root msgid "Exchange records" msgstr "" +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__id +msgid "ID" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order____last_update +msgid "Last Modified on" +msgstr "" + #. module: edi_purchase_oca #: model:ir.model,name:edi_purchase_oca.model_purchase_order msgid "Purchase Order" @@ -37,3 +97,13 @@ msgstr "" #: model:ir.ui.menu,name:edi_purchase_oca.menu_purchase_edi_exchange_record msgid "Purchase Orders" msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order__origin_edi_endpoint_id +msgid "Record generated via this endpoint" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order__disable_edi_auto +msgid "When marked, EDI automatic processing will be avoided" +msgstr "" diff --git a/edi_purchase_oca/static/description/index.html b/edi_purchase_oca/static/description/index.html index b7baa690b..844cb4033 100644 --- a/edi_purchase_oca/static/description/index.html +++ b/edi_purchase_oca/static/description/index.html @@ -1,20 +1,20 @@ - + - + EDI Purchase - - -
    -

    EDI Purchase

    - - -

    Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

    -

    This module intends to create a base to be extended by local edi rules -for purchase.

    -

    In order to add a new integration, you need to create a listener:

    -
    -class MyEventListener(Component):
    -    _name = "purchase.order.event.listener.demo"
    -    _inherit = "base.event.listener"
    -    _apply_on = ["purchase.order"]
    -
    -    def on_button_confirm_purchase_order(self, move):
    -        """Add your code here"""
    -
    -

    Table of contents

    - -
    -

    Bug Tracker

    -

    Bugs are tracked on GitHub Issues. -In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

    -

    Do not contact contributors directly about support or help with technical issues.

    -
    -
    -

    Credits

    -
    -

    Authors

    -
      -
    • ForgeFlow
    • -
    -
    - -
    -

    Maintainers

    -

    This module is maintained by the OCA.

    -Odoo Community Association -

    OCA, or the Odoo Community Association, is a nonprofit organization whose -mission is to support the collaborative development of Odoo features and -promote its widespread use.

    -

    This module is part of the OCA/edi-framework project on GitHub.

    -

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    -
    -
    -
    - + + + + EDI Purchase + + + +
    +

    EDI Purchase

    + + +

    + + Beta + + + License: LGPL-3 + + + OCA/edi-framework + + + Translate me on Weblate + + + Try me on Runboat + +

    +

    This module intends to create a base to be extended by local edi rules + for purchase. +

    +

    In order to add a new integration, you need to create a listener:

    +
    +                class
    +                MyEventListener
    +                (
    +                Component
    +                ):
    +                
    +                
    +                _name
    +                =
    +                "purchase.order.event.listener.demo"
    +                
    +                
    +                _inherit
    +                =
    +                "base.event.listener"
    +                
    +                
    +                _apply_on
    +                =
    +                [
    +                "purchase.order"
    +                ]
    +                
    +
    +                
    +                def
    +                on_button_confirm_purchase_order
    +                (
    +                self
    +                ,
    +                move
    +                ):
    +                
    +                
    +                """Add your code here"""
    +            
    +

    + Table of contents +

    +
    + +
    +
    +

    + Bug Tracker +

    +

    Bugs are tracked on + GitHub Issues. + In case of trouble, please check there if your issue has already been reported. + If you spotted it first, help us to smash it by providing a detailed and welcomed + + feedback. +

    +

    Do not contact contributors directly about support or help with technical issues.

    +
    +
    +

    + Credits +

    +
    +

    + Authors +

    +
      +
    • ForgeFlow
    • +
    +
    +
    +

    + Contributors +

    + +
    +
    +

    + Maintainers +

    +

    This module is maintained by the OCA.

    + + Odoo Community Association + +

    OCA, or the Odoo Community Association, is a nonprofit organization whose + mission is to support the collaborative development of Odoo features and + promote its widespread use. +

    +

    This module is part of the + OCA/edi-framework + + project on GitHub. +

    +

    You are welcome to contribute. To learn how please visit + https://odoo-community.org/page/Contribute. +

    +
    +
    +
    + diff --git a/edi_purchase_oca/views/purchase_order_views.xml b/edi_purchase_oca/views/purchase_order_views.xml index 6e17a3a3e..b6d4c6799 100644 --- a/edi_purchase_oca/views/purchase_order_views.xml +++ b/edi_purchase_oca/views/purchase_order_views.xml @@ -7,12 +7,22 @@ purchase.order + + + + + + + + + + + + + + + + + diff --git a/edi_purchase_oca/views/res_partner_view.xml b/edi_purchase_oca/views/res_partner_view.xml index f950cc0d3..4985d326e 100644 --- a/edi_purchase_oca/views/res_partner_view.xml +++ b/edi_purchase_oca/views/res_partner_view.xml @@ -1,11 +1,10 @@ - res.partner.edi.purchase res.partner - + From a577be17eece5d27b5c707b0340c102f9c03fe51 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 15:09:52 +0200 Subject: [PATCH 29/42] [DOC] edi_purchase: update contributors --- edi_purchase_oca/readme/CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/edi_purchase_oca/readme/CONTRIBUTORS.md b/edi_purchase_oca/readme/CONTRIBUTORS.md index a18e9df0d..1ea50d1f7 100644 --- a/edi_purchase_oca/readme/CONTRIBUTORS.md +++ b/edi_purchase_oca/readme/CONTRIBUTORS.md @@ -1,3 +1,4 @@ * Lois Rilo * Simone Orsi * Phan Hong Phuc \<\> +* Maksym Yankin \ No newline at end of file From 9375a5ed88b77f13779ed217f03ad55c12ece3bb Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 20 May 2026 16:56:08 +0200 Subject: [PATCH 30/42] edi_purchase_oca: rework test setup --- edi_purchase_oca/tests/common.py | 13 ++----------- edi_purchase_oca/tests/test_generate.py | 2 +- edi_purchase_oca/tests/test_order.py | 11 ++++++++++- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/edi_purchase_oca/tests/common.py b/edi_purchase_oca/tests/common.py index 00f7f74e7..80d99c031 100644 --- a/edi_purchase_oca/tests/common.py +++ b/edi_purchase_oca/tests/common.py @@ -69,9 +69,9 @@ def _create_purchase_order(cls, **kw): return model.create(po_vals) @classmethod - def _setup_order(cls): + def _setup_order_records(cls): cls.vendor = cls.env["res.partner"].create( - {"name": "Azure Interior", "country_id": cls.env.company.country_id.id} + {"name": "ACME inc", "country_id": cls.env.company.country_id.id} ) cls.product = cls.env["product.product"].create( { @@ -80,12 +80,3 @@ def _setup_order(cls): "purchase_ok": True, } ) - return { - "order_line": [ - { - "product_id": cls.product.id, - "product_qty": 10, - "price_unit": 100.0, - } - ], - } diff --git a/edi_purchase_oca/tests/test_generate.py b/edi_purchase_oca/tests/test_generate.py index 1d10c3374..0f814b911 100644 --- a/edi_purchase_oca/tests/test_generate.py +++ b/edi_purchase_oca/tests/test_generate.py @@ -63,7 +63,7 @@ def setUpClass(cls): "snippet_do": cls._snippet_tpl.format(state="cancel"), } ) - cls._setup_order() + cls._setup_order_records() def test_new_order_no_conf_no_output(self): # No conf linked to the vendor -> no snippet executed. diff --git a/edi_purchase_oca/tests/test_order.py b/edi_purchase_oca/tests/test_order.py index 3285fa516..4fc5ad212 100644 --- a/edi_purchase_oca/tests/test_order.py +++ b/edi_purchase_oca/tests/test_order.py @@ -16,7 +16,16 @@ def setUpClass(cls): cls.exc_record_in = cls.backend.create_record( cls.exchange_type_in.code, {"edi_exchange_state": "input_received"} ) - order_vals = cls._setup_order() + cls._setup_order_records() + order_vals = { + "order_line": [ + { + "product_id": cls.product.id, + "product_qty": 10, + "price_unit": 100.0, + } + ], + } cls.order = cls._create_purchase_order( origin_exchange_record_id=cls.exc_record_in.id, **order_vals, From d1bb54e6f1c85ba50574d7a239f9a04db9732741 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Sun, 24 May 2026 15:45:48 +0000 Subject: [PATCH 31/42] [UPD] Update edi_purchase_oca.pot --- edi_purchase_oca/i18n/edi_purchase_oca.pot | 72 +++++++++++++++++----- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/edi_purchase_oca/i18n/edi_purchase_oca.pot b/edi_purchase_oca/i18n/edi_purchase_oca.pot index 3cf50cd17..249b4e6aa 100644 --- a/edi_purchase_oca/i18n/edi_purchase_oca.pot +++ b/edi_purchase_oca/i18n/edi_purchase_oca.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 14.0\n" +"Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -19,68 +19,110 @@ msgid "EDI" msgstr "" #. module: edi_purchase_oca -#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__disable_edi_auto +#: model:ir.model,name:edi_purchase_oca.model_res_partner +msgid "Contact" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__edi_disable_auto +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__edi_disable_auto msgid "Disable auto" msgstr "" #. module: edi_purchase_oca -#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__display_name -msgid "Display Name" +#: model_terms:ir.ui.view,arch_db:edi_purchase_oca.purchase_order_form +msgid "Disable automated actions" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.ui.menu,name:edi_purchase_oca.menu_purchase_edi_root +#: model_terms:ir.ui.view,arch_db:edi_purchase_oca.purchase_order_form +msgid "EDI" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__edi_id +msgid "EDI ID" msgstr "" #. module: edi_purchase_oca #: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__origin_edi_endpoint_id +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__origin_edi_endpoint_id msgid "EDI origin endpoint" msgstr "" #. module: edi_purchase_oca #: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__origin_exchange_type_id +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__origin_exchange_type_id msgid "EDI origin exchange type" msgstr "" #. module: edi_purchase_oca #: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__origin_exchange_record_id +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__origin_exchange_record_id msgid "EDI origin record" msgstr "" +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_res_partner__edi_purchase_conf_ids +#: model:ir.model.fields,field_description:edi_purchase_oca.field_res_users__edi_purchase_conf_ids +msgid "EDI purchase configuration" +msgstr "" + #. module: edi_purchase_oca #: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order__origin_exchange_record_id +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order_line__origin_exchange_record_id msgid "EDI record that originated this document." msgstr "" #. module: edi_purchase_oca #: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__edi_config +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__edi_config msgid "Edi Config" msgstr "" +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__edi_exchange_ready +msgid "Edi Exchange Ready" +msgstr "" + #. module: edi_purchase_oca #: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__edi_has_form_config +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__edi_has_form_config msgid "Edi Has Form Config" msgstr "" #. module: edi_purchase_oca #: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__exchange_record_ids +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__exchange_record_ids msgid "Exchange Record" msgstr "" #. module: edi_purchase_oca #: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__exchange_record_count +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__exchange_record_count msgid "Exchange Record Count" msgstr "" #. module: edi_purchase_oca -#: model:ir.ui.menu,name:edi_purchase_oca.menu_purchase_edi_root -msgid "Exchange records" +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__exchange_related_record_ids +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__exchange_related_record_ids +msgid "Exchange Related Record" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.ui.menu,name:edi_purchase_oca.menu_purchase_edi_exchange_record +msgid "Exchanges" msgstr "" #. module: edi_purchase_oca -#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__id -msgid "ID" +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order_line__edi_id +msgid "Internal or external identifier for records." msgstr "" #. module: edi_purchase_oca -#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order____last_update -msgid "Last Modified on" +#: model_terms:ir.ui.view,arch_db:edi_purchase_oca.view_partner_form +msgid "Purchase" msgstr "" #. module: edi_purchase_oca @@ -90,20 +132,22 @@ msgstr "" #. module: edi_purchase_oca #: model:ir.actions.act_window,name:edi_purchase_oca.act_open_edi_exchange_record_purchase_order_view -msgid "Purchase Order Exchange Record" +msgid "Purchase Order Exchange Records" msgstr "" #. module: edi_purchase_oca -#: model:ir.ui.menu,name:edi_purchase_oca.menu_purchase_edi_exchange_record -msgid "Purchase Orders" +#: model:ir.model,name:edi_purchase_oca.model_purchase_order_line +msgid "Purchase Order Line" msgstr "" #. module: edi_purchase_oca #: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order__origin_edi_endpoint_id +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order_line__origin_edi_endpoint_id msgid "Record generated via this endpoint" msgstr "" #. module: edi_purchase_oca -#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order__disable_edi_auto +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order__edi_disable_auto +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order_line__edi_disable_auto msgid "When marked, EDI automatic processing will be avoided" msgstr "" From 6f12a27dd1813e5bcc314009668acf0cfa783c7c Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sun, 24 May 2026 15:52:45 +0000 Subject: [PATCH 32/42] [BOT] post-merge updates --- README.md | 1 + edi_purchase_oca/README.rst | 18 +++++++--- .../static/description/index.html | 33 ++++++++++++------- setup/_metapackage/pyproject.toml | 3 +- 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5e016a5ca..5406a0b2f 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ addon | version | maintainers | summary [edi_oca](edi_oca/) | 18.0.1.5.2 | simahawk etobella | Integrate all EDI modules together [edi_party_data_oca](edi_party_data_oca/) | 18.0.1.0.1 | simahawk | Allow to configure and retrieve party information for EDI exchanges. [edi_product_oca](edi_product_oca/) | 18.0.1.0.0 | | EDI framework configuration and base logic for products and products packaging +[edi_purchase_oca](edi_purchase_oca/) | 18.0.1.0.0 | | Define EDI Configuration for Purchase Orders [edi_queue_oca](edi_queue_oca/) | 18.0.1.0.2 | | Set Queue Jobs on EDI [edi_record_metadata_oca](edi_record_metadata_oca/) | 18.0.1.0.5 | simahawk | Allow to store metadata for related records. [edi_sale_endpoint](edi_sale_endpoint/) | 18.0.1.0.0 | simahawk | Glue module between edi_sale_oca and edi_endpoint_oca. diff --git a/edi_purchase_oca/README.rst b/edi_purchase_oca/README.rst index c86a88f7a..e35506069 100644 --- a/edi_purchase_oca/README.rst +++ b/edi_purchase_oca/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ============ EDI Purchase ============ @@ -7,13 +11,13 @@ EDI Purchase !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:8ab40ab13f4a26ea0ba04ff19e53af88e490d1b2e783699454ae6255422e00c3 + !! source digest: sha256:35895d0baca724ca82a2652ae75fb72231316d24ea998ba0cfeeb49648bcfa83 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi--framework-lightgray.png?logo=github @@ -28,8 +32,12 @@ EDI Purchase |badge1| |badge2| |badge3| |badge4| |badge5| -This module intends to create a base to be extended by local edi rules -for purchase. Assign the EDI Configuration on the partner form. +Handle purchase orders via EDI. + +This is a base module to plug purchase processes with the EDI framework. + +To handle inbound/outbound purchase orders, you need to create your own +integration modules on top of this base module. **Table of contents** @@ -60,8 +68,8 @@ Contributors - Lois Rilo lois.rilo@forgeflow.com - Simone Orsi simone.orsi@camptocamp.com - - Phan Hong Phuc +- Maksym Yankin maksym.yankin@camptocamp.com Maintainers ----------- diff --git a/edi_purchase_oca/static/description/index.html b/edi_purchase_oca/static/description/index.html index f8825ba72..228082825 100644 --- a/edi_purchase_oca/static/description/index.html +++ b/edi_purchase_oca/static/description/index.html @@ -3,7 +3,7 @@ -EDI Purchase +README.rst -
    -

    EDI Purchase

    +
    + + +Odoo Community Association + +
    +

    EDI Purchase

    -

    Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

    -

    This module intends to create a base to be extended by local edi rules -for purchase. Assign the EDI Configuration on the partner form.

    +

    Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

    +

    Handle purchase orders via EDI.

    +

    This is a base module to plug purchase processes with the EDI framework.

    +

    To handle inbound/outbound purchase orders, you need to create your own +integration modules on top of this base module.

    Table of contents

      @@ -385,7 +392,7 @@

      EDI Purchase

    -

    Bug Tracker

    +

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -393,24 +400,25 @@

    Bug Tracker

    Do not contact contributors directly about support or help with technical issues.

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • ForgeFlow
    • Camptocamp
    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association @@ -423,5 +431,6 @@

    Maintainers

    +
    diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml index 988bd3ff5..abcb4d0a4 100644 --- a/setup/_metapackage/pyproject.toml +++ b/setup/_metapackage/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "odoo-addons-oca-edi-framework" -version = "18.0.20260514.0" +version = "18.0.20260524.0" dependencies = [ "odoo-addon-edi_account_core_oca==18.0.*", "odoo-addon-edi_account_oca==18.0.*", @@ -13,6 +13,7 @@ dependencies = [ "odoo-addon-edi_oca==18.0.*", "odoo-addon-edi_party_data_oca==18.0.*", "odoo-addon-edi_product_oca==18.0.*", + "odoo-addon-edi_purchase_oca==18.0.*", "odoo-addon-edi_queue_oca==18.0.*", "odoo-addon-edi_record_metadata_oca==18.0.*", "odoo-addon-edi_sale_endpoint==18.0.*", From de24b9522379263b05403f655f0e9798f934fbb7 Mon Sep 17 00:00:00 2001 From: mymage Date: Mon, 25 May 2026 19:48:53 +0000 Subject: [PATCH 33/42] Translated using Weblate (Italian) Currently translated at 100.0% (285 of 285 strings) Translation: edi-framework-18.0/edi-framework-18.0-edi_core_oca Translate-URL: https://translation.odoo-community.org/projects/edi-framework-18-0/edi-framework-18-0-edi_core_oca/it/ --- edi_core_oca/i18n/it.po | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/edi_core_oca/i18n/it.po b/edi_core_oca/i18n/it.po index 737c2dedc..3147d8f34 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-04-30 10:45+0000\n" +"PO-Revision-Date: 2026-05-25 19:57+0000\n" "Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" @@ -1102,7 +1102,7 @@ msgstr "Generatore" #. module: edi_core_oca #: model:ir.model.fields,field_description:edi_core_oca.field_edi_configuration__is_global msgid "Global Configuration" -msgstr "" +msgstr "Configurazione globale" #. module: edi_core_oca #: model_terms:ir.ui.view,arch_db:edi_core_oca.edi_exchange_record_view_search @@ -1181,6 +1181,8 @@ msgid "" "If checked, this configuration will be executed for all records, regardless " "of the partner relation." msgstr "" +"Se selezionata, questa configurazione verrà eseguita per tutti i record, " +"indipendentemente dalla relazione con il partner." #. module: edi_core_oca #: model:ir.model.fields,help:edi_core_oca.field_edi_exchange_type__exchange_filename_sequence_id From e03e10a3823c3d6a0dbd412db5c02b2b521b2246 Mon Sep 17 00:00:00 2001 From: mymage Date: Tue, 26 May 2026 06:17:22 +0000 Subject: [PATCH 34/42] Added translation using Weblate (Italian) --- edi_purchase_oca/i18n/it.po | 154 ++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 edi_purchase_oca/i18n/it.po diff --git a/edi_purchase_oca/i18n/it.po b/edi_purchase_oca/i18n/it.po new file mode 100644 index 000000000..dde2677a8 --- /dev/null +++ b/edi_purchase_oca/i18n/it.po @@ -0,0 +1,154 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * edi_purchase_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: edi_purchase_oca +#: model_terms:ir.ui.view,arch_db:edi_purchase_oca.purchase_order_form +msgid "EDI" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model,name:edi_purchase_oca.model_res_partner +msgid "Contact" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__edi_disable_auto +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__edi_disable_auto +msgid "Disable auto" +msgstr "" + +#. module: edi_purchase_oca +#: model_terms:ir.ui.view,arch_db:edi_purchase_oca.purchase_order_form +msgid "Disable automated actions" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.ui.menu,name:edi_purchase_oca.menu_purchase_edi_root +#: model_terms:ir.ui.view,arch_db:edi_purchase_oca.purchase_order_form +msgid "EDI" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__edi_id +msgid "EDI ID" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__origin_edi_endpoint_id +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__origin_edi_endpoint_id +msgid "EDI origin endpoint" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__origin_exchange_type_id +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__origin_exchange_type_id +msgid "EDI origin exchange type" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__origin_exchange_record_id +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__origin_exchange_record_id +msgid "EDI origin record" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_res_partner__edi_purchase_conf_ids +#: model:ir.model.fields,field_description:edi_purchase_oca.field_res_users__edi_purchase_conf_ids +msgid "EDI purchase configuration" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order__origin_exchange_record_id +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order_line__origin_exchange_record_id +msgid "EDI record that originated this document." +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__edi_config +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__edi_config +msgid "Edi Config" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__edi_exchange_ready +msgid "Edi Exchange Ready" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__edi_has_form_config +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__edi_has_form_config +msgid "Edi Has Form Config" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__exchange_record_ids +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__exchange_record_ids +msgid "Exchange Record" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__exchange_record_count +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__exchange_record_count +msgid "Exchange Record Count" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order__exchange_related_record_ids +#: model:ir.model.fields,field_description:edi_purchase_oca.field_purchase_order_line__exchange_related_record_ids +msgid "Exchange Related Record" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.ui.menu,name:edi_purchase_oca.menu_purchase_edi_exchange_record +msgid "Exchanges" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order_line__edi_id +msgid "Internal or external identifier for records." +msgstr "" + +#. module: edi_purchase_oca +#: model_terms:ir.ui.view,arch_db:edi_purchase_oca.view_partner_form +msgid "Purchase" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model,name:edi_purchase_oca.model_purchase_order +msgid "Purchase Order" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.actions.act_window,name:edi_purchase_oca.act_open_edi_exchange_record_purchase_order_view +msgid "Purchase Order Exchange Records" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model,name:edi_purchase_oca.model_purchase_order_line +msgid "Purchase Order Line" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order__origin_edi_endpoint_id +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order_line__origin_edi_endpoint_id +msgid "Record generated via this endpoint" +msgstr "" + +#. module: edi_purchase_oca +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order__edi_disable_auto +#: model:ir.model.fields,help:edi_purchase_oca.field_purchase_order_line__edi_disable_auto +msgid "When marked, EDI automatic processing will be avoided" +msgstr "" From 7df845212ffd457a917bb76b371b6d413653b98b Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 20 Dec 2024 11:35:09 +0100 Subject: [PATCH 35/42] Add edi_purchase_ubl_output_oca --- edi_purchase_ubl_output_oca/README.rst | 1 + edi_purchase_ubl_output_oca/__init__.py | 1 + edi_purchase_ubl_output_oca/__manifest__.py | 18 +++++++++++++ .../components/__init__.py | 1 + .../components/generate.py | 27 +++++++++++++++++++ .../demo/edi_exchange_type.xml | 20 ++++++++++++++ .../readme/CONFIGURATION.rst | 7 +++++ .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 5 ++++ 9 files changed, 81 insertions(+) create mode 100644 edi_purchase_ubl_output_oca/README.rst create mode 100644 edi_purchase_ubl_output_oca/__init__.py create mode 100644 edi_purchase_ubl_output_oca/__manifest__.py create mode 100644 edi_purchase_ubl_output_oca/components/__init__.py create mode 100644 edi_purchase_ubl_output_oca/components/generate.py create mode 100644 edi_purchase_ubl_output_oca/demo/edi_exchange_type.xml create mode 100644 edi_purchase_ubl_output_oca/readme/CONFIGURATION.rst create mode 100644 edi_purchase_ubl_output_oca/readme/CONTRIBUTORS.rst create mode 100644 edi_purchase_ubl_output_oca/readme/DESCRIPTION.rst diff --git a/edi_purchase_ubl_output_oca/README.rst b/edi_purchase_ubl_output_oca/README.rst new file mode 100644 index 000000000..a74415d00 --- /dev/null +++ b/edi_purchase_ubl_output_oca/README.rst @@ -0,0 +1 @@ +wait for the bot diff --git a/edi_purchase_ubl_output_oca/__init__.py b/edi_purchase_ubl_output_oca/__init__.py new file mode 100644 index 000000000..1377f57f5 --- /dev/null +++ b/edi_purchase_ubl_output_oca/__init__.py @@ -0,0 +1 @@ +from . import components diff --git a/edi_purchase_ubl_output_oca/__manifest__.py b/edi_purchase_ubl_output_oca/__manifest__.py new file mode 100644 index 000000000..0b2a232c2 --- /dev/null +++ b/edi_purchase_ubl_output_oca/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2021 Camptocamp SA +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "EDI UBL Purchase", + "summary": """Handle outbound exchanges for purchases.""", + "version": "14.0.1.0.0", + "development_status": "Alpha", + "license": "AGPL-3", + "website": "https://github.com/OCA/edi", + "author": "Camptocamp, Odoo Community Association (OCA)", + "maintainers": ["simahawk"], + "depends": ["edi_purchase_oca", "edi_ubl_oca", "purchase_order_ubl"], + "demo": [ + "demo/edi_exchange_type.xml", + ], +} diff --git a/edi_purchase_ubl_output_oca/components/__init__.py b/edi_purchase_ubl_output_oca/components/__init__.py new file mode 100644 index 000000000..f839b0d3b --- /dev/null +++ b/edi_purchase_ubl_output_oca/components/__init__.py @@ -0,0 +1 @@ +from . import generate diff --git a/edi_purchase_ubl_output_oca/components/generate.py b/edi_purchase_ubl_output_oca/components/generate.py new file mode 100644 index 000000000..557344312 --- /dev/null +++ b/edi_purchase_ubl_output_oca/components/generate.py @@ -0,0 +1,27 @@ +# Copyright 2021 Camptocamp SA +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +from odoo.addons.component.core import Component + + +class EDIExchangePOGenerate(Component): + """Generate purchase orders.""" + + _name = "edi.output.ubl.purchase.order" + _inherit = "edi.component.output.mixin" + _usage = "output.generate.purchase.order" + + def generate(self): + return self._generate_ubl_xml() + + # TODO: add tests + def _generate_ubl_xml(self): + order = self.record + doc_type = order.get_ubl_purchase_order_doc_type() + if not doc_type: + raise NotImplementedError("TODO: handle no doc type") + version = order.get_ubl_version() + xml_string = order.generate_ubl_xml_string(doc_type, version=version) + return xml_string diff --git a/edi_purchase_ubl_output_oca/demo/edi_exchange_type.xml b/edi_purchase_ubl_output_oca/demo/edi_exchange_type.xml new file mode 100644 index 000000000..5c0428ba7 --- /dev/null +++ b/edi_purchase_ubl_output_oca/demo/edi_exchange_type.xml @@ -0,0 +1,20 @@ + + + + + Demo UBL PO out + demo_UBL_PO_out + output + {record_name}-{type.code}-{dt} + xml + + +components: + generate: + usage: output.generate.purchase.order + + + diff --git a/edi_purchase_ubl_output_oca/readme/CONFIGURATION.rst b/edi_purchase_ubl_output_oca/readme/CONFIGURATION.rst new file mode 100644 index 000000000..df94d54c5 --- /dev/null +++ b/edi_purchase_ubl_output_oca/readme/CONFIGURATION.rst @@ -0,0 +1,7 @@ +On your exchange type configured for UBL outbound exchanges use this conf in `advanced_settings_edit`:: + + components: + generate: + usage: output.generate.purchase.order + +That's it. diff --git a/edi_purchase_ubl_output_oca/readme/CONTRIBUTORS.rst b/edi_purchase_ubl_output_oca/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..f1c71bce1 --- /dev/null +++ b/edi_purchase_ubl_output_oca/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Simone Orsi diff --git a/edi_purchase_ubl_output_oca/readme/DESCRIPTION.rst b/edi_purchase_ubl_output_oca/readme/DESCRIPTION.rst new file mode 100644 index 000000000..41f35c7a4 --- /dev/null +++ b/edi_purchase_ubl_output_oca/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +Handle purchase exchanges with the EDI framework. + +This module is mostly a glue module for `purchase_order_ubl` with `edi_oca`. + +Allows you to generate and send purchase orders as UBL XML files. From 5fd95bc29744d89c461119b2238a076ec4103322 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Wed, 11 Jun 2025 13:27:16 +0000 Subject: [PATCH 36/42] [UPD] Update edi_purchase_ubl_output_oca.pot --- .../i18n/edi_purchase_ubl_output_oca.pot | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 edi_purchase_ubl_output_oca/i18n/edi_purchase_ubl_output_oca.pot diff --git a/edi_purchase_ubl_output_oca/i18n/edi_purchase_ubl_output_oca.pot b/edi_purchase_ubl_output_oca/i18n/edi_purchase_ubl_output_oca.pot new file mode 100644 index 000000000..4d8b20f91 --- /dev/null +++ b/edi_purchase_ubl_output_oca/i18n/edi_purchase_ubl_output_oca.pot @@ -0,0 +1,13 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" From 702c6ddcd3083e6cda35ad776c02a4948f2e9ddb Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 11 Jun 2025 13:36:23 +0000 Subject: [PATCH 37/42] [BOT] post-merge updates --- edi_purchase_ubl_output_oca/README.rst | 98 +++- edi_purchase_ubl_output_oca/__manifest__.py | 2 +- .../static/description/icon.png | Bin 0 -> 10254 bytes .../static/description/index.html | 439 ++++++++++++++++++ 4 files changed, 537 insertions(+), 2 deletions(-) create mode 100644 edi_purchase_ubl_output_oca/static/description/icon.png create mode 100644 edi_purchase_ubl_output_oca/static/description/index.html diff --git a/edi_purchase_ubl_output_oca/README.rst b/edi_purchase_ubl_output_oca/README.rst index a74415d00..022885c57 100644 --- a/edi_purchase_ubl_output_oca/README.rst +++ b/edi_purchase_ubl_output_oca/README.rst @@ -1 +1,97 @@ -wait for the bot +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +================ +EDI UBL Purchase +================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:64740d2084509ea5c625aeb34e7e20f2a11ffa3a8e3cb2e13c107092a4b438b2 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github + :target: https://github.com/OCA/edi/tree/14.0/edi_purchase_ubl_output_oca + :alt: OCA/edi +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/edi-14-0/edi-14-0-edi_purchase_ubl_output_oca + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/edi&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Handle purchase exchanges with the EDI framework. + +This module is mostly a glue module for `purchase_order_ubl` with `edi_oca`. + +Allows you to generate and send purchase orders as UBL XML files. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Simone Orsi + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-simahawk| image:: https://github.com/simahawk.png?size=40px + :target: https://github.com/simahawk + :alt: simahawk + +Current `maintainer `__: + +|maintainer-simahawk| + +This module is part of the `OCA/edi `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/edi_purchase_ubl_output_oca/__manifest__.py b/edi_purchase_ubl_output_oca/__manifest__.py index 0b2a232c2..d9cac3e5b 100644 --- a/edi_purchase_ubl_output_oca/__manifest__.py +++ b/edi_purchase_ubl_output_oca/__manifest__.py @@ -5,7 +5,7 @@ { "name": "EDI UBL Purchase", "summary": """Handle outbound exchanges for purchases.""", - "version": "14.0.1.0.0", + "version": "14.0.1.1.0", "development_status": "Alpha", "license": "AGPL-3", "website": "https://github.com/OCA/edi", diff --git a/edi_purchase_ubl_output_oca/static/description/icon.png b/edi_purchase_ubl_output_oca/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1dcc49c24f364e9adf0afbc6fc0bac6dbecdeb11 GIT binary patch literal 10254 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 + + + + +README.rst + + + +
    + + + +Odoo Community Association + +
    +

    EDI UBL Purchase

    + +

    Alpha License: AGPL-3 OCA/edi Translate me on Weblate Try me on Runboat

    +

    Handle purchase exchanges with the EDI framework.

    +

    This module is mostly a glue module for purchase_order_ubl with edi_oca.

    +

    Allows you to generate and send purchase orders as UBL XML files.

    +
    +

    Important

    +

    This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

    +
    +

    Table of contents

    + +
    +

    Bug Tracker

    +

    Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

    +

    Do not contact contributors directly about support or help with technical issues.

    +
    +
    +

    Credits

    +
    +

    Authors

    +
      +
    • Camptocamp
    • +
    +
    + +
    +

    Maintainers

    +

    This module is maintained by the OCA.

    + +Odoo Community Association + +

    OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

    +

    Current maintainer:

    +

    simahawk

    +

    This module is part of the OCA/edi project on GitHub.

    +

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    +
    +
    +
    +
    + + From 36639926a83d778a6687db6e87915c99969fe4bf Mon Sep 17 00:00:00 2001 From: mymage Date: Tue, 22 Jul 2025 14:31:59 +0000 Subject: [PATCH 38/42] Added translation using Weblate (Italian) --- edi_purchase_ubl_output_oca/i18n/it.po | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 edi_purchase_ubl_output_oca/i18n/it.po diff --git a/edi_purchase_ubl_output_oca/i18n/it.po b/edi_purchase_ubl_output_oca/i18n/it.po new file mode 100644 index 000000000..9ce4346f6 --- /dev/null +++ b/edi_purchase_ubl_output_oca/i18n/it.po @@ -0,0 +1,14 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" From fed7949770104d35cf2f1c06044c023e1ed669d6 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 15:13:10 +0200 Subject: [PATCH 39/42] [IMP] edi_purchase_ubl_output_oca: pre-commit auto fixes --- edi_purchase_ubl_output_oca/__manifest__.py | 2 +- edi_purchase_ubl_output_oca/pyproject.toml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 edi_purchase_ubl_output_oca/pyproject.toml diff --git a/edi_purchase_ubl_output_oca/__manifest__.py b/edi_purchase_ubl_output_oca/__manifest__.py index d9cac3e5b..af0946dd8 100644 --- a/edi_purchase_ubl_output_oca/__manifest__.py +++ b/edi_purchase_ubl_output_oca/__manifest__.py @@ -8,7 +8,7 @@ "version": "14.0.1.1.0", "development_status": "Alpha", "license": "AGPL-3", - "website": "https://github.com/OCA/edi", + "website": "https://github.com/OCA/edi-framework", "author": "Camptocamp, Odoo Community Association (OCA)", "maintainers": ["simahawk"], "depends": ["edi_purchase_oca", "edi_ubl_oca", "purchase_order_ubl"], diff --git a/edi_purchase_ubl_output_oca/pyproject.toml b/edi_purchase_ubl_output_oca/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/edi_purchase_ubl_output_oca/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" From a39bae1a94025d7eaea93041a415237a63f04a94 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 15:19:42 +0200 Subject: [PATCH 40/42] [MIG] edi_purchase_ubl_output_oca: migrate to v18 --- edi_purchase_ubl_output_oca/__init__.py | 2 +- edi_purchase_ubl_output_oca/__manifest__.py | 2 +- .../demo/edi_exchange_type.xml | 9 ++------- .../{components => models}/__init__.py | 0 .../{components => models}/generate.py | 19 +++++++++---------- 5 files changed, 13 insertions(+), 19 deletions(-) rename edi_purchase_ubl_output_oca/{components => models}/__init__.py (100%) rename edi_purchase_ubl_output_oca/{components => models}/generate.py (59%) diff --git a/edi_purchase_ubl_output_oca/__init__.py b/edi_purchase_ubl_output_oca/__init__.py index 1377f57f5..0650744f6 100644 --- a/edi_purchase_ubl_output_oca/__init__.py +++ b/edi_purchase_ubl_output_oca/__init__.py @@ -1 +1 @@ -from . import components +from . import models diff --git a/edi_purchase_ubl_output_oca/__manifest__.py b/edi_purchase_ubl_output_oca/__manifest__.py index af0946dd8..c9fcbbc7e 100644 --- a/edi_purchase_ubl_output_oca/__manifest__.py +++ b/edi_purchase_ubl_output_oca/__manifest__.py @@ -5,7 +5,7 @@ { "name": "EDI UBL Purchase", "summary": """Handle outbound exchanges for purchases.""", - "version": "14.0.1.1.0", + "version": "18.0.1.0.0", "development_status": "Alpha", "license": "AGPL-3", "website": "https://github.com/OCA/edi-framework", diff --git a/edi_purchase_ubl_output_oca/demo/edi_exchange_type.xml b/edi_purchase_ubl_output_oca/demo/edi_exchange_type.xml index 5c0428ba7..8d8cca6d1 100644 --- a/edi_purchase_ubl_output_oca/demo/edi_exchange_type.xml +++ b/edi_purchase_ubl_output_oca/demo/edi_exchange_type.xml @@ -8,13 +8,8 @@ {record_name}-{type.code}-{dt} xml - -components: - generate: - usage: output.generate.purchase.order - diff --git a/edi_purchase_ubl_output_oca/components/__init__.py b/edi_purchase_ubl_output_oca/models/__init__.py similarity index 100% rename from edi_purchase_ubl_output_oca/components/__init__.py rename to edi_purchase_ubl_output_oca/models/__init__.py diff --git a/edi_purchase_ubl_output_oca/components/generate.py b/edi_purchase_ubl_output_oca/models/generate.py similarity index 59% rename from edi_purchase_ubl_output_oca/components/generate.py rename to edi_purchase_ubl_output_oca/models/generate.py index 557344312..8ef69669c 100644 --- a/edi_purchase_ubl_output_oca/components/generate.py +++ b/edi_purchase_ubl_output_oca/models/generate.py @@ -2,23 +2,22 @@ # @author: Simone Orsi # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models -from odoo.addons.component.core import Component - -class EDIExchangePOGenerate(Component): +class EDIExchangePOGenerate(models.AbstractModel): """Generate purchase orders.""" + _description = "UBL output generator for purchase orders" + _name = "edi.output.ubl.purchase.order" - _inherit = "edi.component.output.mixin" - _usage = "output.generate.purchase.order" + _inherit = "edi.oca.handler.generate" - def generate(self): - return self._generate_ubl_xml() + def generate(self, exchange_record): + return self._generate_ubl_xml(exchange_record) - # TODO: add tests - def _generate_ubl_xml(self): - order = self.record + def _generate_ubl_xml(self, exchange_record): + order = exchange_record.record doc_type = order.get_ubl_purchase_order_doc_type() if not doc_type: raise NotImplementedError("TODO: handle no doc type") From bf4108b931473b95d4927c1ff1d7144f3c01b676 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 14 May 2026 16:28:10 +0200 Subject: [PATCH 41/42] [COV] edi_purchase_ubl_output_oca: add test cov --- edi_purchase_ubl_output_oca/tests/__init__.py | 1 + .../tests/test_generate.py | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 edi_purchase_ubl_output_oca/tests/__init__.py create mode 100644 edi_purchase_ubl_output_oca/tests/test_generate.py diff --git a/edi_purchase_ubl_output_oca/tests/__init__.py b/edi_purchase_ubl_output_oca/tests/__init__.py new file mode 100644 index 000000000..427d09a22 --- /dev/null +++ b/edi_purchase_ubl_output_oca/tests/__init__.py @@ -0,0 +1 @@ +from . import test_generate diff --git a/edi_purchase_ubl_output_oca/tests/test_generate.py b/edi_purchase_ubl_output_oca/tests/test_generate.py new file mode 100644 index 000000000..9cddc6dc5 --- /dev/null +++ b/edi_purchase_ubl_output_oca/tests/test_generate.py @@ -0,0 +1,98 @@ +# Copyright 2026 Camptocamp SA +# @author Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + +from odoo.addons.purchase_order_ubl.tests.common import PurchaseOrderUblMixin + + +class TestPurchaseUBLOutputGenerate(PurchaseOrderUblMixin, TransactionCase): + """Ensure ``edi.output.ubl.purchase.order`` produces a valid UBL XML file.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env( + context=dict( + cls.env.context, tracking_disable=True, edi__skip_quick_exec=True + ) + ) + cls._setup_purchase_ubl_records() + cls.backend_type = cls.env.ref("edi_ubl_oca.edi_backend_type_ubl") + cls.backend = cls.env["edi.backend"].create( + { + "name": "UBL purchase test backend", + "backend_type_id": cls.backend_type.id, + } + ) + generator_model = cls.env["ir.model"]._get("edi.output.ubl.purchase.order") + cls.exc_type = cls.env["edi.exchange.type"].create( + { + "name": "Test UBL PO out", + "code": "test_ubl_po_out", + "direction": "output", + "exchange_file_ext": "xml", + "exchange_filename_pattern": "{record.id}-{type.code}-{dt}", + "backend_id": cls.backend.id, + "backend_type_id": cls.backend_type.id, + "generate_model_id": generator_model.id, + } + ) + + def _create_exchange_record(self): + return self.backend.create_record( + self.exc_type.code, + {"model": self.order._name, "res_id": self.order.id}, + ) + + def _generate_xml(self, version): + record = self._create_exchange_record() + record.with_context(ubl_version=version).action_exchange_generate() + self.assertTrue(record.exchange_file) + return record._get_file_content() + + def test_generate_order_confirmed(self): + self.order.button_confirm() + self.assertEqual(self.order.state, "purchase") + for version in ("2.1", "2.2"): + with self.subTest(version=version): + xml_string = self._generate_xml(version) + self._assert_valid_ubl_xml(xml_string, "Order", version) + + def test_generate_rfq(self): + self.assertIn(self.order.state, self.order.get_rfq_states()) + for version in ("2.1", "2.2"): + with self.subTest(version=version): + xml_string = self._generate_xml(version) + self._assert_valid_ubl_xml(xml_string, "RequestForQuotation", version) + + def test_generate_skip_taxes_via_advanced_settings(self): + """``advanced_settings_edit`` must propagate ``env_ctx`` to generate. + + Setting ``ubl_add_item__skip_taxes: true`` on the ``generate`` + component env_ctx must result in no ``ClassifiedTaxCategory`` + nodes in the produced UBL XML. + """ + self.exc_type.advanced_settings_edit = ( + "execution_model:\n" + " generate:\n" + " env_ctx:\n" + " ubl_add_item__skip_taxes: true\n" + ) + self.order.button_confirm() + xml_string = self._generate_xml("2.1") + parsed = self._assert_valid_ubl_xml(xml_string, "Order", "2.1") + tax_nodes = self._classified_tax_categories(parsed) + self.assertFalse( + tax_nodes, + "ClassifiedTaxCategory must be skipped when " + "ubl_add_item__skip_taxes is set via advanced_settings_edit", + ) + + def test_generate_taxes_included_by_default(self): + """Without any env_ctx, ClassifiedTaxCategory must be present.""" + self.order.button_confirm() + xml_string = self._generate_xml("2.1") + parsed = self._assert_valid_ubl_xml(xml_string, "Order", "2.1") + self.assertTrue(self._classified_tax_categories(parsed)) From f6f3682f235c9859676e5da8112b66c4162836ab Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 26 May 2026 10:46:55 +0200 Subject: [PATCH 42/42] [DOC] edi_purchase_ubl_output_oca: improve README --- .../readme/CONFIGURATION.rst | 23 +++++++++++++++---- .../readme/DESCRIPTION.rst | 3 ++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/edi_purchase_ubl_output_oca/readme/CONFIGURATION.rst b/edi_purchase_ubl_output_oca/readme/CONFIGURATION.rst index df94d54c5..a02530592 100644 --- a/edi_purchase_ubl_output_oca/readme/CONFIGURATION.rst +++ b/edi_purchase_ubl_output_oca/readme/CONFIGURATION.rst @@ -1,7 +1,20 @@ -On your exchange type configured for UBL outbound exchanges use this conf in `advanced_settings_edit`:: +On your exchange type configured for UBL outbound exchanges +select "UBL output generator for purchase orders" as "Generator". - components: - generate: - usage: output.generate.purchase.order +Example of full flow configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -That's it. +1. Create an exchange type for UBL output, with "UBL output generator for purchase orders" as generator. +2. Create an `edi.configuration` for purchase orders with + + a. the exchange type created at step 1, + b. the model `purchase.order`, + c. the trigger "On PO state change" + d. snippet like + + if record.state == 'purchase': + record._edi_send_via_edi(conf.type_id) + + +3. Assign the configuration to a supplier +4. Create a PO for that supplier and confirm the order diff --git a/edi_purchase_ubl_output_oca/readme/DESCRIPTION.rst b/edi_purchase_ubl_output_oca/readme/DESCRIPTION.rst index 41f35c7a4..561e6b748 100644 --- a/edi_purchase_ubl_output_oca/readme/DESCRIPTION.rst +++ b/edi_purchase_ubl_output_oca/readme/DESCRIPTION.rst @@ -2,4 +2,5 @@ Handle purchase exchanges with the EDI framework. This module is mostly a glue module for `purchase_order_ubl` with `edi_oca`. -Allows you to generate and send purchase orders as UBL XML files. +Allows you to generate and send purchase orders as UBL XML files +with a simple configuration.