diff --git a/edi_core_oca/models/edi_backend.py b/edi_core_oca/models/edi_backend.py index 00a21fa42..045799be6 100644 --- a/edi_core_oca/models/edi_backend.py +++ b/edi_core_oca/models/edi_backend.py @@ -16,6 +16,7 @@ from odoo.exceptions import UserError from ..exceptions import EDINotImplementedError, EDIValidationError +from ..utils import EdiExchangeActionResult _logger = logging.getLogger(__name__) @@ -126,8 +127,12 @@ def exchange_generate(self, exchange_record, store=True, force=False, **kw): # Remove file to regenerate exchange_record.exchange_file = False self._check_exchange_generate(exchange_record, force=force) - output = self._exchange_generate(exchange_record, **kw) - message = None + action_result = self._ensure_action_result( + self._exchange_generate(exchange_record, **kw), + default_message=exchange_record._exchange_status_message("generate_ok"), + ) + output = action_result.output + message = action_result.message encoding = exchange_record.type_id.encoding or "UTF-8" encoding_error_handler = ( exchange_record.type_id.encoding_out_error_handler or "strict" @@ -142,7 +147,6 @@ def exchange_generate(self, exchange_record, store=True, force=False, **kw): } ) if output: - message = exchange_record._exchange_status_message("generate_ok") try: with self.env.cr.savepoint(): self._validate_data(exchange_record, output) @@ -203,7 +207,16 @@ def _check_exchange_generate(self, exchange_record, force=False): def _exchange_generate(self, exchange_record, **kw): exchange_function = self._get_exec_handler(exchange_record, "generate") ctx = self._get_record_env_ctx(exchange_record, "generate") - return exchange_function(exchange_record.with_context(**ctx), **kw) + result = exchange_function(exchange_record.with_context(**ctx), **kw) + return self._ensure_action_result( + result, + default_message=exchange_record._exchange_status_message("generate_ok"), + ) + + def _ensure_action_result(self, result, default_message=None): + if isinstance(result, EdiExchangeActionResult): + return result + return EdiExchangeActionResult(output=result, message=default_message) # TODO: add tests def _validate_data(self, exchange_record, value=None, **kw): @@ -244,7 +257,9 @@ def exchange_send(self, exchange_record): res = "" try: with self.env.cr.savepoint(): - self._exchange_send(exchange_record) + send_result = self._ensure_action_result( + self._exchange_send(exchange_record) + ) _logger.debug("%s sent", exchange_record.identifier) except self._send_retryable_exceptions() as err: traceback = _get_exception_traceback() @@ -269,15 +284,18 @@ def exchange_send(self, exchange_record): res = "__sql_error__" raise else: - # TODO: maybe the send handler should return desired message and state - message = exchange_record._exchange_status_message("send_ok") + message = ( + send_result.message + or send_result.output + or exchange_record._exchange_status_message("send_ok") + ) error = traceback = None state = ( "output_sent_and_processed" if self.output_sent_processed_auto else "output_sent" ) - res = message + res = send_result.output or message finally: if res != "__sql_error__": exchange_record.write( @@ -329,7 +347,8 @@ def _output_check_send(self, exchange_record): def _exchange_send(self, exchange_record): exchange_function = self._get_exec_handler(exchange_record, "send") ctx = self._get_record_env_ctx(exchange_record, "send") - return exchange_function(exchange_record.with_context(**ctx)) + result = exchange_function(exchange_record.with_context(**ctx)) + return self._ensure_action_result(result) def _cron_check_output_exchange_sync(self, **kw): for backend in self: @@ -475,7 +494,9 @@ def exchange_process(self, exchange_record): res = None try: with self.env.cr.savepoint(): - res = self._exchange_process(exchange_record) + process_result = self._exchange_process(exchange_record) + res = process_result.output + message = process_result.message except self._swallable_exceptions() as err: if self.env.context.get("_edi_process_break_on_error"): raise @@ -517,7 +538,8 @@ def exchange_process(self, exchange_record): def _exchange_process(self, exchange_record): exchange_function = self._get_exec_handler(exchange_record, "process") ctx = self._get_record_env_ctx(exchange_record, "process") - return exchange_function(exchange_record.with_context(**ctx)) + result = exchange_function(exchange_record.with_context(**ctx)) + return self._ensure_action_result(result) def exchange_receive(self, exchange_record): """Retrieve an incoming document.""" @@ -533,7 +555,11 @@ def exchange_receive(self, exchange_record): res = None try: with self.env.cr.savepoint(): - content = self._exchange_receive(exchange_record) + receive_result = self._ensure_action_result( + self._exchange_receive(exchange_record) + ) + content = receive_result.output + message = receive_result.message # Ignore result of FileNotFoundError/OSError if content is not None: exchange_record._set_file_content(content) @@ -556,7 +582,7 @@ def exchange_receive(self, exchange_record): res = "__sql_error__" raise else: - message = exchange_record._exchange_status_message("receive_ok") + message = message or exchange_record._exchange_status_message("receive_ok") error = traceback = None state = "input_received" res = message @@ -598,7 +624,8 @@ def _exchange_receive_check(self, exchange_record): def _exchange_receive(self, exchange_record): exchange_function = self._get_exec_handler(exchange_record, "receive") ctx = self._get_record_env_ctx(exchange_record, "receive") - return exchange_function(exchange_record.with_context(**ctx)) + result = exchange_function(exchange_record.with_context(**ctx)) + return self._ensure_action_result(result) def _cron_check_input_exchange_sync(self, **kw): for backend in self: diff --git a/edi_core_oca/tests/test_backend_output.py b/edi_core_oca/tests/test_backend_output.py index 2b5ea7d41..c136656a9 100644 --- a/edi_core_oca/tests/test_backend_output.py +++ b/edi_core_oca/tests/test_backend_output.py @@ -12,6 +12,7 @@ from odoo.exceptions import UserError from odoo.orm.model_classes import add_to_registry +from ..utils import EdiExchangeActionResult from .common import EDIBackendCommonTestCase @@ -54,6 +55,31 @@ def test_generate_record_output(self): self.record.with_context(fake_output="yeah!").action_exchange_generate() self.assertEqual(self.record._get_file_content(), "yeah!") + def test_exchange_generate_wraps_legacy_result(self): + result = self.record.with_context( + fake_output="yeah!" + ).backend_id._exchange_generate(self.record) + self.assertEqual(result.output, "yeah!") + self.assertEqual( + result.message, self.record._exchange_status_message("generate_ok") + ) + + def test_generate_record_output_with_custom_action_result_message(self): + with mock.patch.object(type(self.backend), "_exchange_generate") as mocked: + mocked.return_value = EdiExchangeActionResult( + output="yeah!", message="Generated with custom message" + ) + message = self.record.action_exchange_generate() + self.assertEqual(message, "Generated with custom message") + self.assertEqual(self.record._get_file_content(), "yeah!") + + def test_generate_record_output_with_legacy_override_string(self): + with mock.patch.object(type(self.backend), "_exchange_generate") as mocked: + mocked.return_value = "yeah!" + message = self.record.action_exchange_generate() + self.assertEqual(message, self.record._exchange_status_message("generate_ok")) + self.assertEqual(self.record._get_file_content(), "yeah!") + def test_generate_record_output_pdf(self): pdf_content = tools.file_open( "addons/edi_core_oca/tests/result.pdf", mode="rb" @@ -77,6 +103,17 @@ def test_send_record(self): "2020-10-21 10:00:00", ) + def test_send_record_with_custom_action_result(self): + self.record.write({"edi_exchange_state": "output_pending"}) + self.record._set_file_content(f"TEST {self.record.id}") + with mock.patch.object(type(self.backend), "_exchange_send") as mocked: + mocked.return_value = EdiExchangeActionResult( + output="send-payload", message="Sent with custom message" + ) + res = self.record.action_exchange_send() + self.assertEqual(res, "send-payload") + self.assertRecordValues(self.record, [{"edi_exchange_state": "output_sent"}]) + def test_send_record_with_error(self): self.record.write({"edi_exchange_state": "output_pending"}) self.record._set_file_content(f"TEST {self.record.id}") diff --git a/edi_core_oca/utils.py b/edi_core_oca/utils.py index e9fe9a8c1..aa0e1cb47 100644 --- a/edi_core_oca/utils.py +++ b/edi_core_oca/utils.py @@ -3,6 +3,14 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). import hashlib +from dataclasses import dataclass +from typing import Any + + +@dataclass +class EdiExchangeActionResult: + output: Any = None + message: str | None = None def normalize_string(cls, a_string, sep="_"): diff --git a/edi_queue_oca/tests/test_backend_jobs.py b/edi_queue_oca/tests/test_backend_jobs.py index 61ac106bd..34d663180 100644 --- a/edi_queue_oca/tests/test_backend_jobs.py +++ b/edi_queue_oca/tests/test_backend_jobs.py @@ -54,7 +54,7 @@ def _get_related_jobs(self, record): action = record.action_view_related_queue_jobs() return self.env["queue.job"].search(action["domain"]) - def test_output(self): + def _test_output_return(self, message, expected_message): job_counter = self.job_counter() vals = { "model": self.partner._name, @@ -84,15 +84,21 @@ def test_output(self): job = self.backend.with_delay().exchange_send(record) created = job_counter.search_created() with mock.patch.object(type(self.backend), "_exchange_send") as mocked: - mocked.return_value = "ok" + mocked.return_value = message res = job.perform() - self.assertEqual(res, "Exchange sent") + self.assertEqual(res, expected_message) self.assertEqual(record.edi_exchange_state, "output_sent") self.assertEqual(created[0].name, "Send exchange file.") # Check related jobs record.invalidate_recordset() self.assertEqual(created, self._get_related_jobs(record)) + def test_output_with_specific_return(self): + self._test_output_return("specific return message", "specific return message") + + def test_output_no_return(self): + self._test_output_return("", "Exchange sent") + def test_output_fail_retry(self): job_counter = self.job_counter() vals = {