diff --git a/shopfloor/actions/message.py b/shopfloor/actions/message.py
index e8962bf19bc..5835e401d86 100644
--- a/shopfloor/actions/message.py
+++ b/shopfloor/actions/message.py
@@ -526,12 +526,6 @@ def packaging_not_found_in_picking(self):
"body": _("Packaging not found in the current transfer."),
}
- def packaging_dimension_updated(self, packaging):
- return {
- "message_type": "success",
- "body": _("Packaging {} dimension updated.").format(packaging.name),
- }
-
def expiration_date_missing(self):
return {
"message_type": "error",
diff --git a/shopfloor_reception_packaging_dimension/__init__.py b/shopfloor_reception_packaging_dimension/__init__.py
index e7386302bbe..d354f467795 100644
--- a/shopfloor_reception_packaging_dimension/__init__.py
+++ b/shopfloor_reception_packaging_dimension/__init__.py
@@ -1,3 +1,4 @@
from .hooks import post_init_hook, uninstall_hook
from . import models
from . import services
+from . import actions
diff --git a/shopfloor_reception_packaging_dimension/actions/__init__.py b/shopfloor_reception_packaging_dimension/actions/__init__.py
new file mode 100644
index 00000000000..7bb61dcadf7
--- /dev/null
+++ b/shopfloor_reception_packaging_dimension/actions/__init__.py
@@ -0,0 +1,3 @@
+from . import data
+from . import schema
+from . import message
diff --git a/shopfloor_reception_packaging_dimension/actions/data.py b/shopfloor_reception_packaging_dimension/actions/data.py
new file mode 100644
index 00000000000..d79286dae93
--- /dev/null
+++ b/shopfloor_reception_packaging_dimension/actions/data.py
@@ -0,0 +1,25 @@
+from odoo.addons.component.core import Component
+from odoo.addons.shopfloor_base.utils import ensure_model
+
+
+class DataAction(Component):
+ _inherit = "shopfloor.data.action"
+
+ @property
+ def _packaging_dimension_detail_parser(self):
+ return [
+ "id",
+ "name",
+ "qty",
+ "packaging_length",
+ "width",
+ "height",
+ "weight",
+ "length_uom_name",
+ "weight_uom_name",
+ "barcode",
+ ]
+
+ @ensure_model("product.packaging")
+ def packaging_dimensions(self, record, **kw):
+ return self._jsonify(record, self._packaging_dimension_detail_parser, **kw)
diff --git a/shopfloor_reception_packaging_dimension/actions/message.py b/shopfloor_reception_packaging_dimension/actions/message.py
new file mode 100644
index 00000000000..c15e458fda6
--- /dev/null
+++ b/shopfloor_reception_packaging_dimension/actions/message.py
@@ -0,0 +1,17 @@
+import logging
+
+from odoo import _
+
+from odoo.addons.component.core import Component
+
+_logger = logging.getLogger(__name__)
+
+
+class MessageAction(Component):
+ _inherit = "shopfloor.message.action"
+
+ def packaging_updated(self, packaging):
+ return {
+ "message_type": "success",
+ "body": _("Packaging '{}' updated.").format(packaging.name),
+ }
diff --git a/shopfloor_reception_packaging_dimension/actions/schema.py b/shopfloor_reception_packaging_dimension/actions/schema.py
new file mode 100644
index 00000000000..e982635203e
--- /dev/null
+++ b/shopfloor_reception_packaging_dimension/actions/schema.py
@@ -0,0 +1,27 @@
+from odoo.addons.component.core import Component
+
+
+class ShopfloorSchemaAction(Component):
+ _inherit = "shopfloor.schema.action"
+
+ def packaging_dimensions(self):
+ return {
+ "id": {"required": True, "type": "integer"},
+ "name": {"type": "string", "nullable": False, "required": True},
+ "qty": {"type": "float", "required": True},
+ "packaging_length": {"type": "float", "nullable": True, "required": False},
+ "width": {"type": "float", "nullable": True, "required": False},
+ "height": {"type": "float", "nullable": True, "required": False},
+ "weight": {"type": "float", "nullable": True, "required": False},
+ "length_uom_name": {
+ "type": "string",
+ "nullable": True,
+ "required": False,
+ },
+ "weight_uom_name": {
+ "type": "string",
+ "nullable": True,
+ "required": False,
+ },
+ "barcode": {"type": "string", "nullable": True, "required": False},
+ }
diff --git a/shopfloor_reception_packaging_dimension/services/reception.py b/shopfloor_reception_packaging_dimension/services/reception.py
index bcce750e136..f44a671ddce 100644
--- a/shopfloor_reception_packaging_dimension/services/reception.py
+++ b/shopfloor_reception_packaging_dimension/services/reception.py
@@ -17,13 +17,16 @@ def __init__(self, work_context):
def _before_state__set_quantity(self, picking, line, message=None):
"""Show the packaging dimension screen before the set quantity screen."""
- if self.work.menu.set_packaging_dimension and not self.packaging_update_done:
- packaging = self._get_next_packaging_to_set_dimension(line.product_id)
- if packaging:
- return self._response_for_set_packaging_dimension(
- picking, line, packaging, message=message
- )
- return super()._before_state__set_quantity(picking, line, message=message)
+ if not self.work.menu.set_packaging_dimension or self.packaging_update_done:
+ return super()._before_state__set_quantity(picking, line, message=message)
+
+ packaging = self._get_next_packaging_to_set_dimension(line.product_id)
+ if not packaging:
+ return super()._before_state__set_quantity(picking, line, message=message)
+
+ return self._response_for_set_packaging_dimension(
+ picking, line, packaging, message=message
+ )
def _get_domain_packaging_needs_dimension(self):
return expression.OR(
@@ -86,14 +89,14 @@ def _response_for_set_packaging_dimension(
)
def _set_packaging_dimension_data_for_packaging(self, packaging):
- return self.data_detail.packaging_detail(packaging)
+ return self.data.packaging_dimensions(packaging)
def set_packaging_dimension(
- self, picking_id, selected_line_id, packaging_id, cancel=False, **kwargs
+ self, picking_id, selected_line_id, packaging_id, skip=False, **kwargs
):
"""Set the dimension on a product packaging.
- If the user cancel the dimension update we still propose the next
+ If the user skip the dimension update we still propose the next
possible packaging.
Transitions:
@@ -103,38 +106,48 @@ def set_packaging_dimension(
picking = self.env["stock.picking"].browse(picking_id)
selected_line = self.env["stock.move.line"].browse(selected_line_id)
packaging = self.env["product.packaging"].sudo().browse(packaging_id)
- message = None
- next_packaging = None
+
if not packaging:
- message = self.msg_store.record_not_found()
- elif not cancel and self._check_dimension_to_update(kwargs):
- self._update_packaging_dimension(packaging, kwargs)
- message = self.msg_store.packaging_dimension_updated(packaging)
- if packaging:
- next_packaging = self._get_next_packaging_to_set_dimension(
- selected_line.product_id, packaging
+ return self._before_state__set_quantity(
+ picking, selected_line, message=self.msg_store.record_not_found()
)
+
+ message = None
+
+ if not skip and self._check_dimension_to_update(kwargs):
+ self._update_packaging_dimension(packaging, kwargs)
+ message = self.msg_store.packaging_updated(packaging)
+
+ next_packaging = self._get_next_packaging_to_set_dimension(
+ selected_line.product_id, packaging
+ )
if next_packaging:
return self._response_for_set_packaging_dimension(
picking, selected_line, next_packaging, message=message
)
+
self.packaging_update_done = True
return self._before_state__set_quantity(picking, selected_line, message=message)
def _check_dimension_to_update(self, dimensions):
- """Return True if any dimension on the packaging needs to be updated"""
- return any([value is not None for key, value in dimensions.items()])
-
- def _get_dimension_fields_conversion_map(self):
- return {"length": "packaging_length"}
+ """Check if the Shopfloor payload contains data for a packaging update."""
+ return any(value is not None for value in dimensions.values())
def _update_packaging_dimension(self, packaging, dimensions_to_update):
"""Update dimension on the packaging."""
- fields_conv_map = self._get_dimension_fields_conversion_map()
- for dimension, value in dimensions_to_update.items():
- if value is not None:
- dimension = fields_conv_map.get(dimension, dimension)
- packaging[dimension] = value
+ values_to_update = {}
+ packaging_values = packaging.read(dimensions_to_update.keys())[0]
+
+ for key, value in dimensions_to_update.items():
+ if value is None:
+ continue
+ # Skip updating fields with unchanged values to prevent unnecessary
+ # triggers of compute methods or other side effects
+ if packaging_values[key] != value:
+ values_to_update[key] = value
+
+ if values_to_update:
+ packaging.write(values_to_update)
class ShopfloorReceptionValidator(Component):
@@ -155,7 +168,7 @@ def set_packaging_dimension(self):
"type": "float",
"nullable": True,
},
- "length": {
+ "packaging_length": {
"coerce": to_float,
"required": False,
"type": "float",
@@ -186,7 +199,7 @@ def set_packaging_dimension(self):
"nullable": True,
},
"barcode": {"type": "string", "required": False, "nullable": True},
- "cancel": {"type": "boolean"},
+ "skip": {"type": "boolean"},
}
@@ -213,13 +226,13 @@ def _schema_set_packaging_dimension(self):
return {
"picking": {"type": "dict", "schema": self.schemas.picking()},
"selected_move_line": {"type": "dict", "schema": self.schemas.move_line()},
- "packaging": self._schema_packaging(),
+ "packaging": self._schema_packaging_dimensions(),
}
- def _schema_packaging(self):
+ def _schema_packaging_dimensions(self):
return {
"type": "dict",
- "schema": self.schemas_detail.packaging_detail(),
+ "schema": self.schemas.packaging_dimensions(),
}
def _set_packaging_dimension_next_states(self):
diff --git a/shopfloor_reception_packaging_dimension/tests/test_set_package_dimension.py b/shopfloor_reception_packaging_dimension/tests/test_set_package_dimension.py
index d04d9e335c7..41360e23221 100644
--- a/shopfloor_reception_packaging_dimension/tests/test_set_package_dimension.py
+++ b/shopfloor_reception_packaging_dimension/tests/test_set_package_dimension.py
@@ -53,7 +53,7 @@ def _assert_response_set_dimension(
data = {
"picking": self.data.picking(picking),
"selected_move_line": self.data.move_line(line),
- "packaging": self.data_detail.packaging_detail(packaging),
+ "packaging": self.data.packaging_dimensions(packaging),
}
self.assert_response(
response,
@@ -206,7 +206,7 @@ def test_set_multiple_packaging_dimension(self):
"selected_line_id": line.id,
"packaging_id": self.product_c_packaging.id,
"height": 55,
- "length": 233,
+ "packaging_length": 233,
},
)
self.assertEqual(self.product_c_packaging.height, 55)
@@ -216,9 +216,7 @@ def test_set_multiple_packaging_dimension(self):
self.picking,
line,
self.product_c_packaging_2,
- message=self.msg_store.packaging_dimension_updated(
- self.product_c_packaging
- ),
+ message=self.msg_store.packaging_updated(self.product_c_packaging),
)
response = self.service.dispatch(
"set_packaging_dimension",
@@ -240,7 +238,35 @@ def test_set_multiple_packaging_dimension(self):
"selected_move_line": self.data.move_lines(line),
"confirmation_required": None,
},
- message=self.msg_store.packaging_dimension_updated(
- self.product_c_packaging_2
- ),
+ message=self.msg_store.packaging_updated(self.product_c_packaging_2),
+ )
+
+ def test_skip_packaging_dimension_skips_to_next(self):
+ line = self.picking.move_line_ids.filtered(
+ lambda li: li.product_id == self.product_c
+ )
+ original_height = self.product_c_packaging.height
+
+ response = self.service.dispatch(
+ "set_packaging_dimension",
+ params={
+ "picking_id": self.picking.id,
+ "selected_line_id": line.id,
+ "packaging_id": self.product_c_packaging.id,
+ "height": 999.0, # This value should be ignored
+ "skip": True,
+ },
+ )
+
+ self.assertEqual(
+ self.product_c_packaging.height,
+ original_height,
+ "Packaging height should not change when skipped",
+ )
+ self._assert_response_set_dimension(
+ response,
+ self.picking,
+ line,
+ self.product_c_packaging_2,
+ message=None, # No 'Updated' message should be returned when skipping
)
diff --git a/shopfloor_reception_packaging_dimension_mobile/static/src/scenario/reception_packaging_dimension.js b/shopfloor_reception_packaging_dimension_mobile/static/src/scenario/reception_packaging_dimension.js
index 30138f958c8..bb143fa79e6 100644
--- a/shopfloor_reception_packaging_dimension_mobile/static/src/scenario/reception_packaging_dimension.js
+++ b/shopfloor_reception_packaging_dimension_mobile/static/src/scenario/reception_packaging_dimension.js
@@ -6,7 +6,6 @@
import {process_registry} from "/shopfloor_mobile_base/static/wms/src/services/process_registry.js";
const reception_scenario = process_registry.get("reception");
-const _get_states = reception_scenario.component.methods._get_states;
// Get the original template of the reception scenario
const template = reception_scenario.component.template;
// And inject the new state template (for this module) into it
@@ -19,7 +18,7 @@ const new_template =
@@ -28,7 +27,7 @@ const new_template =
@@ -36,46 +35,45 @@ const new_template =
label="Quantiy"
type="number"
placeholder="Packaging Quantity"
- v-model="state.data.packaging.qty"
+ v-model="state.data.packaging.qty_input"
>
-
@@ -101,42 +99,159 @@ const new_template =
// Extend the reception scenario with :
// - the new patched template
// - the js code for the new state
+const _get_states_base = reception_scenario.component.methods._get_states;
+const baseWatchers = reception_scenario.component.watch || {};
+const baseMethods = reception_scenario.component.methods || {};
const ReceptionPackageDimension = process_registry.extend("reception", {
template: new_template,
- "methods.get_packaging_measurements": function () {
- return ["length", "width", "height", "weight", "qty", "barcode"];
+ watch: {
+ ...baseWatchers,
+ "state.key": function (newState) {
+ if (newState === "set_packaging_dimension") {
+ this.prefill_packaging_form_inputs();
+ }
+ },
},
- "methods._get_states": function () {
- let states = _get_states.bind(this)();
- states["set_packaging_dimension"] = {
- display_info: {
- title: "Set packaging dimension",
- },
- events: {
- go_back: "on_back",
- },
- get_payload_set_packaging_dimension: () => {
- let values = {
- picking_id: this.state.data.picking.id,
- selected_line_id: this.state.data.selected_move_line.id,
- packaging_id: this.state.data.packaging.id,
- };
- for (const measurement of this.get_packaging_measurements()) {
- values[measurement] = this.state.data.packaging[measurement];
+ methods: {
+ ...baseMethods,
+ prefill_packaging_form_inputs: function () {
+ if (!this.state_is("set_packaging_dimension")) return;
+
+ const pkg = this.state.data.packaging;
+ const input_fields = this.get_packaging_measurements_inputs();
+
+ input_fields.forEach((inputKey) => {
+ const originalKey = inputKey.replace("_input", "");
+ if (pkg[inputKey] === undefined || pkg[inputKey] === null) {
+ this.$set(pkg, inputKey, pkg[originalKey]);
}
- return values;
- },
- on_skip: () => {
- const payload = this.state.get_payload_set_packaging_dimension();
- payload["cancel"] = true;
- this.wait_call(this.odoo.call("set_packaging_dimension", payload));
- },
- on_done: () => {
- const payload = this.state.get_payload_set_packaging_dimension();
- this.wait_call(this.odoo.call("set_packaging_dimension", payload));
- },
- };
- return states;
+ });
+ },
+ get_packaging_measurements_inputs: function () {
+ return [
+ "packaging_length_input",
+ "width_input",
+ "height_input",
+ "weight_input",
+ "qty_input",
+ "barcode_input",
+ ];
+ },
+ packaging_detail_options: function () {
+ const pkg = this.state.data.packaging;
+ const _is_field_changed = (fieldName) => {
+ const inputKey = fieldName + "_input";
+ return pkg[inputKey] && pkg[inputKey] != pkg[fieldName];
+ };
+ const options = {
+ main: true,
+ key_title: "name",
+ title_icon: "mdi-package-variant",
+ fields: [
+ {
+ path: "barcode",
+ label: "Barcode",
+ klass: _is_field_changed("barcode") ? "accent" : "",
+ },
+ {
+ path: "qty",
+ label: "Quantity",
+ klass: _is_field_changed("qty") ? "accent" : "",
+ },
+ {
+ path: "packaging_length",
+ label: "Length",
+ klass: _is_field_changed("packaging_length") ? "accent" : "",
+ renderer: function (rec, field) {
+ const value = _.result(rec, "packaging_length", "");
+ const uom = _.result(rec, "length_uom_name", "");
+ return value + " " + uom;
+ },
+ },
+ {
+ path: "width",
+ label: "Width",
+ klass: _is_field_changed("width") ? "accent" : "",
+ renderer: function (rec, field) {
+ const value = _.result(rec, "width", "");
+ const uom = _.result(rec, "length_uom_name", "");
+ return value + " " + uom;
+ },
+ },
+ {
+ path: "height",
+ label: "Height",
+ klass: _is_field_changed("height") ? "accent" : "",
+ renderer: function (rec, field) {
+ const value = _.result(rec, "height", "");
+ const uom = _.result(rec, "length_uom_name", "");
+ return value + " " + uom;
+ },
+ },
+ {
+ path: "weight",
+ label: "Weight",
+ klass: _is_field_changed("weight") ? "accent" : "",
+ renderer: function (rec, field) {
+ const value = _.result(rec, "weight", "");
+ const uom = _.result(rec, "weight_uom_name", "");
+ return value + " " + uom;
+ },
+ },
+ ],
+ };
+ return options;
+ },
+ _get_states: function () {
+ let states = _get_states_base.bind(this)();
+
+ // Capture 'this' in a variable to be safe across async boundaries
+ const self = this;
+
+ states["set_packaging_dimension"] = {
+ display_info: {
+ title: "Set packaging dimension",
+ },
+ events: {
+ go_back: "on_back",
+ },
+ _get_payload_set_packaging_dimension: () => {
+ let values = {
+ picking_id: this.state.data.picking.id,
+ selected_line_id: this.state.data.selected_move_line.id,
+ packaging_id: this.state.data.packaging.id,
+ };
+ for (const measurement of this.get_packaging_measurements_inputs()) {
+ values[measurement.replace("_input", "")] =
+ this.state.data.packaging[measurement];
+ }
+ return values;
+ },
+ _handle_dimension_submission: async function (is_skip = false) {
+ const payload = self.state._get_payload_set_packaging_dimension();
+ if (is_skip) {
+ payload["skip"] = true;
+ }
+
+ await self.wait_call(
+ self.odoo.call("set_packaging_dimension", payload)
+ );
+
+ // Prepare next screen
+ self.prefill_packaging_form_inputs();
+ self.$nextTick(() => {
+ window.scrollTo(0, 0);
+ });
+ },
+ on_skip: async function () {
+ await self.state._handle_dimension_submission(true);
+ },
+ on_done: async function () {
+ await self.state._handle_dimension_submission(false);
+ },
+ };
+ return states;
+ },
},
});