diff --git a/shopfloor/actions/message.py b/shopfloor/actions/message.py index e8962bf19bc..3ed29059dfb 100644 --- a/shopfloor/actions/message.py +++ b/shopfloor/actions/message.py @@ -657,12 +657,17 @@ def location_content_transfer_complete(self, location_src, location_dest): ), } - def location_content_unable_to_transfer(self, location_dest): + def location_content_unable_to_transfer(self, move, location, location_dest): + message = _( + "The content of %(location)s cannot be transferred to " + "%(location_dest)s with this scenario for product %(product_name)s.", + location=location.name, + location_dest=location_dest.name, + product_name=move.product_id.display_name, + ) return { "message_type": "error", - "body": _( - "The content of {} cannot be transferred with this scenario." - ).format(location_dest.name), + "body": message, } def product_in_multiple_sublocation(self, product): diff --git a/shopfloor/services/location_content_transfer.py b/shopfloor/services/location_content_transfer.py index e68bdcc8b65..86771895450 100644 --- a/shopfloor/services/location_content_transfer.py +++ b/shopfloor/services/location_content_transfer.py @@ -386,12 +386,13 @@ def scan_location(self, barcode): # noqa: C901 move_lines = new_moves.move_line_ids for line in move_lines: if not self.is_dest_location_valid(line.move_id, line.location_dest_id): - savepoint.rollback() - return self._response_for_start( - message=self.msg_store.location_content_unable_to_transfer( - location - ) + move = line.move_id + location_dest = line.location_id + message = self.msg_store.location_content_unable_to_transfer( + move, location, location_dest ) + savepoint.rollback() + return self._response_for_start(message=message) stock = self._actions_for("stock") if self.work.menu.ignore_no_putaway_available and stock.no_putaway_available( diff --git a/shopfloor/tests/test_location_content_transfer_putaway.py b/shopfloor/tests/test_location_content_transfer_putaway.py index fb073f8bbc8..90326b89649 100644 --- a/shopfloor/tests/test_location_content_transfer_putaway.py +++ b/shopfloor/tests/test_location_content_transfer_putaway.py @@ -129,15 +129,19 @@ def test_putaway_move_dest_not_child_of_picking_type_dest(self): response = self.service.dispatch( "scan_location", params={"barcode": self.test_loc.barcode} ) + current_moves = self.env["stock.move"].search( + [("location_id", "=", self.test_loc.id), ("state", "=", "assigned")] + ) + self.assertEqual(existing_moves, current_moves) + + message = { + "message_type": "error", + "body": "The content of test cannot be transferred to test with " + "this scenario for product [A] Product A.", + } self.assert_response( response, next_state="scan_location", data=self.ANY, - message=self.service.msg_store.location_content_unable_to_transfer( - self.test_loc - ), + message=message, ) - current_moves = self.env["stock.move"].search( - [("location_id", "=", self.test_loc.id), ("state", "=", "assigned")] - ) - self.assertEqual(existing_moves, current_moves) diff --git a/shopfloor_base/actions/message.py b/shopfloor_base/actions/message.py index 4c8e111772f..7143e02b354 100644 --- a/shopfloor_base/actions/message.py +++ b/shopfloor_base/actions/message.py @@ -1,9 +1,17 @@ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com) # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + from odoo import _ from odoo.addons.component.core import Component +MESSAGE_TYPES = { + "info": 0, + "success": 1, + "warning": 2, + "error": 3, +} + class MessageAction(Component): """Provide message templates @@ -19,6 +27,23 @@ class MessageAction(Component): _inherit = "shopfloor.process.action" _usage = "message" + # A list of ShopfloorMessage + _message_queue = [] + + @property + def message_queue(self): + return self._message_queue + + def add_message(self, value, message_type="info"): + if not isinstance(value, str): + raise TypeError("You should set a string to message queue!") + if message_type not in MESSAGE_TYPES.keys(): + raise TypeError("You should use a correct Shopfloor message type!") + self._message_queue.append(ShopfloorMessage(value, message_type)) + + def clear_queue(self): + self._message_queue = [] + def generic_record_not_found(self): return { "message_type": "error", @@ -46,3 +71,13 @@ def generic_record_not_found(self): # then all depending modules can simply create records they need # instea of overriding and polluting the component. # Additional goodie: users can edit messages via UI. + + +class ShopfloorMessage: + + body = str() + message_type = str() + + def __init__(self, body, message_type, **kwargs): + self.body = body + self.message_type = message_type diff --git a/shopfloor_base/services/service.py b/shopfloor_base/services/service.py index d537e323b01..ea13e930e49 100644 --- a/shopfloor_base/services/service.py +++ b/shopfloor_base/services/service.py @@ -12,6 +12,7 @@ from odoo.addons.component.core import AbstractComponent from ..actions.base_action import get_actions_for +from ..actions.message import MESSAGE_TYPES from ..apispec.service_apispec import ShopfloorRestServiceAPISpec @@ -29,6 +30,8 @@ def __init__(self, work_context): self._profile = getattr(self.work, "profile", self.env["shopfloor.profile"]) self._menu = getattr(self.work, "menu", self.env["shopfloor.menu"]) + self._msg_store = self._actions_for("message") + def _get_api_spec(self, **params): return ShopfloorRestServiceAPISpec(self, **params) @@ -71,6 +74,45 @@ def _to_json(self, records): res.append(self._convert_one_record(record)) return res + def _get_message_type(self, message): + """ + TODO: To be removed if we change the API to support multiple messages + (and types) per result. + """ + message_type = "info" + if message and "message_type" in message: + message_type = message["message_type"] + for message_element in self.msg_store.message_queue: + message_type = ( + message_element.message_type + if MESSAGE_TYPES[message_element.message_type] + > MESSAGE_TYPES[message_type] + else message_type + ) + return message_type + + def _get_message_body(self, message) -> str: + body = "" + if "body" in message: + body = message["body"] + if self.msg_store and self.msg_store.message_queue: + body += "\n".join(element.body for element in self.msg_store.message_queue) + return body + + def _get_message(self, message=None) -> dict: + """ + This will combine the message contained in response and the + messages contained in the message queue + """ + if message is None: + message = {} + message_type = self._get_message_type(message) + body = self._get_message_body(message) + if message_type and body: + message["message_type"] = message_type + message["body"] = body + return message + def _response( self, base_response=None, data=None, next_state=None, message=None, popup=None ): @@ -113,8 +155,9 @@ def _response( elif data: response["data"] = data - if message: + if message := self._get_message(message): response["message"] = message + self.msg_store.clear_queue() if popup: response["popup"] = popup @@ -197,7 +240,7 @@ def schema_detail(self): @property def msg_store(self): - return self._actions_for("message") + return self._msg_store if self._msg_store else self._actions_for("message") # TODO: maybe to be proposed to base_rest # TODO: add tests