diff --git a/shopfloor/actions/message.py b/shopfloor/actions/message.py
index ea8844cb6c..3152b47652 100644
--- a/shopfloor/actions/message.py
+++ b/shopfloor/actions/message.py
@@ -807,3 +807,27 @@ def no_line_to_pack(self):
"message_type": "warning",
"body": _("No line to pack found."),
}
+
+ def invalid_scanned_checkout_object_wo_package(
+ self, scanned_object, package_process_type
+ ):
+ scanned_object_name = {
+ "package": _("a package"),
+ "packaging": _("a packaging"),
+ "delivery_packaging": _("a delivery packaging"),
+ }.get(scanned_object, _("N/A"))
+
+ selection = self.env["shopfloor.menu"]._fields["package_process_type"].selection
+ ppt_name = None
+ for el in selection:
+ if el[0] == package_process_type:
+ ppt_name = el[1]
+ break
+
+ return {
+ "message_type": "error",
+ "body": _(
+ "You scanned {scanned_object_name} which is not allowed"
+ " with the package process type '{ppt_name}'"
+ ).format(scanned_object_name=scanned_object_name, ppt_name=ppt_name),
+ }
diff --git a/shopfloor/data/shopfloor_scenario_data.xml b/shopfloor/data/shopfloor_scenario_data.xml
index c26c67c156..4c2faea3f3 100644
--- a/shopfloor/data/shopfloor_scenario_data.xml
+++ b/shopfloor/data/shopfloor_scenario_data.xml
@@ -43,7 +43,8 @@
{
"no_prefill_qty": true,
"show_oneline_package_content": true,
- "auto_post_line": true
+ "auto_post_line": true,
+ "package_process_type": true
}
diff --git a/shopfloor/models/shopfloor_menu.py b/shopfloor/models/shopfloor_menu.py
index 439c764b4e..b38e36e649 100644
--- a/shopfloor/models/shopfloor_menu.py
+++ b/shopfloor/models/shopfloor_menu.py
@@ -196,6 +196,17 @@ class ShopfloorMenu(models.Model):
compute="_compute_auto_post_line_is_possible"
)
+ package_process_type_is_possible = fields.Boolean(
+ compute="_compute_package_process_type_is_possible"
+ )
+ package_process_type = fields.Selection(
+ [
+ ("without_package", "Without Package"),
+ ("with_package", "With Package"),
+ ],
+ "Package Proccess Type",
+ )
+
@api.onchange("unload_package_at_destination")
def _onchange_unload_package_at_destination(self):
# Uncheck pick_pack_same_time when unload_package_at_destination is set to True
@@ -411,3 +422,10 @@ def _compute_allow_alternative_destination_is_possible(self):
menu.allow_alternative_destination_is_possible = (
menu.scenario_id.has_option("allow_alternative_destination")
)
+
+ @api.depends("scenario_id")
+ def _compute_package_process_type_is_possible(self):
+ for menu in self:
+ menu.package_process_type_is_possible = menu.scenario_id.has_option(
+ "package_process_type"
+ )
diff --git a/shopfloor/services/checkout.py b/shopfloor/services/checkout.py
index 10826b758a..c1630cfefd 100644
--- a/shopfloor/services/checkout.py
+++ b/shopfloor/services/checkout.py
@@ -83,19 +83,28 @@ def _response_for_manual_selection(self, message=None):
return self._response(next_state="manual_selection", data=data, message=message)
def _response_for_select_package(self, picking, lines, message=None):
+ with_pack, wo_pack = self._get_allow_package_options()
return self._response(
next_state="select_package",
data={
"selected_move_lines": self._data_for_move_lines(lines.sorted()),
"picking": self.data.picking(picking),
"packing_info": self._data_for_packing_info(picking),
- "no_package_enabled": not self.options.get(
- "checkout__disable_no_package"
- ),
+ "allow_with_package": with_pack,
+ "allow_without_package": wo_pack,
},
message=message,
)
+ def _get_allow_package_options(self):
+ if not self.work.menu.package_process_type:
+ return True, True
+
+ if self.work.menu.package_process_type == "with_package":
+ return True, False
+
+ return False, True
+
def _data_for_packing_info(self, picking):
"""Return the packing information
@@ -913,11 +922,37 @@ def scan_package_action(self, picking_id, selected_line_ids, barcode):
selected_lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
search_result = self._scan_package_find(picking, barcode)
+
+ message = self._check_scan_package_find_result(search_result)
+ if message:
+ return self._response_for_select_package(
+ picking,
+ selected_lines,
+ message=message,
+ )
+
result_handler = getattr(
self, "_scan_package_action_from_" + search_result.type
)
return result_handler(picking, selected_lines, search_result.record)
+ def _check_scan_package_find_result(self, search_result):
+ ppt = self.work.menu.package_process_type
+ # Currently there is no known way to finish the checkout with a scan. To
+ # process without any package is only possible with a button. In its def
+ # a BadRequest is raised in case no package is not allowed
+ if not ppt or ppt == "with_package":
+ return
+
+ stype = search_result.type
+
+ if ppt == "without_package" and stype in [
+ "package",
+ "packaging",
+ "delivery_packaging",
+ ]:
+ return self.msg_store.invalid_scanned_checkout_object_wo_package(stype, ppt)
+
def _scan_package_find(self, picking, barcode, search_types=None):
search = self._actions_for("search")
search_types = (
@@ -1087,7 +1122,7 @@ def no_package(self, picking_id, selected_line_ids):
Transitions:
* select_line: goes back to selection of lines to work on next lines
"""
- if self.options.get("checkout__disable_no_package"):
+ if self.work.menu.package_process_type == "with_package":
raise BadRequest("`checkout.no_package` endpoint is not enabled")
picking = self.env["stock.picking"].browse(picking_id)
message = self._check_picking_status(picking)
@@ -1562,7 +1597,12 @@ def _states(self):
"select_package": dict(
self._schema_selected_lines,
packing_info={"type": "string", "nullable": True},
- no_package_enabled={
+ allow_with_package={
+ "type": "boolean",
+ "nullable": True,
+ "required": False,
+ },
+ allow_without_package={
"type": "boolean",
"nullable": True,
"required": False,
diff --git a/shopfloor/tests/test_checkout_base.py b/shopfloor/tests/test_checkout_base.py
index deff3aea81..ca47423936 100644
--- a/shopfloor/tests/test_checkout_base.py
+++ b/shopfloor/tests/test_checkout_base.py
@@ -59,7 +59,8 @@ def _assert_select_package_qty_above(self, response, picking):
],
"picking": self._picking_summary_data(picking),
"packing_info": "",
- "no_package_enabled": True,
+ "allow_with_package": True,
+ "allow_without_package": True,
},
message={
"message_type": "warning",
diff --git a/shopfloor/tests/test_checkout_list_delivery_packaging.py b/shopfloor/tests/test_checkout_list_delivery_packaging.py
index 48fc87c446..01aab918a3 100644
--- a/shopfloor/tests/test_checkout_list_delivery_packaging.py
+++ b/shopfloor/tests/test_checkout_list_delivery_packaging.py
@@ -112,9 +112,8 @@ def test_list_delivery_packaging_not_available(self):
self._move_line_data(ml) for ml in selected_lines.sorted()
],
"packing_info": self.service._data_for_packing_info(self.picking),
- "no_package_enabled": not self.service.options.get(
- "checkout__disable_no_package"
- ),
+ "allow_with_package": True,
+ "allow_without_package": True,
},
message=self.service.msg_store.no_delivery_packaging_available(),
)
diff --git a/shopfloor/tests/test_checkout_no_package.py b/shopfloor/tests/test_checkout_no_package.py
index cb53452893..4a11616ee0 100644
--- a/shopfloor/tests/test_checkout_no_package.py
+++ b/shopfloor/tests/test_checkout_no_package.py
@@ -66,8 +66,8 @@ def test_no_package_ok(self):
},
)
- def test_no_package_disabled(self):
- self.service.work.options = {"checkout__disable_no_package": True}
+ def test_without_package_disabled(self):
+ self.menu.sudo().package_process_type = "with_package"
with self.assertRaises(werkzeug.exceptions.BadRequest) as err:
self.service.dispatch(
"no_package",
diff --git a/shopfloor/tests/test_checkout_scan_package_action.py b/shopfloor/tests/test_checkout_scan_package_action.py
index 446f50debd..a5e851ad49 100644
--- a/shopfloor/tests/test_checkout_scan_package_action.py
+++ b/shopfloor/tests/test_checkout_scan_package_action.py
@@ -173,13 +173,83 @@ def test_scan_package_action_scan_package_keep_source_package_error(self):
"picking": self.data.picking(picking),
"selected_move_lines": self.data.move_lines(selected_lines),
"packing_info": self.service._data_for_packing_info(picking),
- "no_package_enabled": not self.service.options.get(
- "checkout__disable_no_package"
- ),
+ "allow_with_package": True,
+ "allow_without_package": True,
},
message=self.service.msg_store.dest_package_not_valid(pack1),
)
+ def test_scan_package_action_scan_package_keep_source_package_error_package_process_type(
+ self,
+ ):
+ ppt = "without_package"
+ self.menu.sudo().package_process_type = ppt
+ picking = self._create_picking(
+ lines=[
+ (self.product_a, 10),
+ (self.product_b, 10),
+ (self.product_c, 10),
+ (self.product_d, 10),
+ ]
+ )
+ pack1_moves = picking.move_lines[:3]
+ pack2_moves = picking.move_lines[3:]
+ # put in 2 packs, for this test, we'll work on pack1
+ self._fill_stock_for_moves(pack1_moves, in_package=True)
+ self._fill_stock_for_moves(pack2_moves, in_package=True)
+ picking.action_assign()
+
+ selected_lines = pack1_moves.move_line_ids
+ pack1 = pack1_moves.move_line_ids.package_id
+
+ move_line1, move_line2, move_line3 = selected_lines
+ # We'll put only product A and B in the package
+ move_line1.qty_done = move_line1.product_uom_qty
+ move_line2.qty_done = move_line2.product_uom_qty
+ move_line3.qty_done = 0
+
+ response = self.service.dispatch(
+ "scan_package_action",
+ params={
+ "picking_id": picking.id,
+ "selected_line_ids": selected_lines.ids,
+ # we try to keep the goods in the same package, so we scan the
+ # source package but this isn't allowed as it is not a delivery
+ # package (i.e. having a delivery packaging set)
+ "barcode": pack1.name,
+ },
+ )
+
+ self.assertRecordValues(
+ move_line1,
+ [{"result_package_id": pack1.id, "shopfloor_checkout_done": False}],
+ )
+ self.assertRecordValues(
+ move_line2,
+ [{"result_package_id": pack1.id, "shopfloor_checkout_done": False}],
+ )
+ self.assertRecordValues(
+ move_line3,
+ # qty_done was zero so it hasn't been done anyway
+ [{"result_package_id": pack1.id, "shopfloor_checkout_done": False}],
+ )
+ self.assert_response(
+ response,
+ # go pack to the screen to select lines to put in packages
+ next_state="select_package",
+ data={
+ "picking": self.data.picking(picking),
+ "selected_move_lines": self.data.move_lines(selected_lines),
+ "packing_info": self.service._data_for_packing_info(picking),
+ "allow_with_package": False,
+ "allow_without_package": True,
+ },
+ message=self.service.msg_store.invalid_scanned_checkout_object_wo_package(
+ "package",
+ ppt,
+ ),
+ )
+
def test_scan_package_action_scan_package_error_invalid(self):
picking = self._create_picking(lines=[(self.product_a, 10)])
move = picking.move_lines
@@ -215,6 +285,44 @@ def test_scan_package_action_scan_package_error_invalid(self):
message=self.service.msg_store.dest_package_not_valid(other_package),
)
+ def test_scan_package_action_scan_package_error_invalid_package_process_type(self):
+ ppt = "without_package"
+ self.menu.sudo().package_process_type = ppt
+ picking = self._create_picking(lines=[(self.product_a, 10)])
+ move = picking.move_lines
+ self._fill_stock_for_moves(move, in_package=True)
+ picking.action_assign()
+
+ selected_line = move.move_line_ids
+ other_package = self.env["stock.quant.package"].create({})
+
+ response = self.service.dispatch(
+ "scan_package_action",
+ params={
+ "picking_id": picking.id,
+ "selected_line_ids": selected_line.ids,
+ "barcode": other_package.name,
+ },
+ )
+
+ self.assertRecordValues(
+ selected_line,
+ [
+ {
+ "result_package_id": selected_line.package_id.id,
+ "shopfloor_checkout_done": False,
+ }
+ ],
+ )
+ self._assert_selected_response(
+ response,
+ selected_line,
+ message=self.service.msg_store.invalid_scanned_checkout_object_wo_package(
+ "package", ppt
+ ),
+ allow_with_package=False,
+ )
+
def test_scan_package_action_scan_package_use_existing_package_ok(self):
picking = self._create_picking(
lines=[
@@ -435,6 +543,99 @@ def test_scan_package_action_scan_packaging_bad_carrier(self):
self.msg_store.goods_packed_in(selected_lines.result_package_id),
)
+ def test_scan_package_action_scan_packaging_invalid_package_process_type(
+ self,
+ ):
+ ppt = "without_package"
+ self.menu.sudo().package_process_type = ppt
+ picking = self._create_picking(lines=[(self.product_a, 10)])
+ picking.carrier_id = picking.carrier_id.search([], limit=1)
+ pack1_moves = picking.move_lines
+ # put in 2 packs, for this test, we'll work on pack1
+ self._fill_stock_for_moves(pack1_moves, in_package=True)
+ picking.action_assign()
+ selected_lines = pack1_moves.move_line_ids
+ selected_lines.qty_done = selected_lines.product_uom_qty
+
+ packaging = (
+ self.env["product.packaging"]
+ .sudo()
+ .create(
+ {
+ "name": "Product Delivery Packaging",
+ "product_id": selected_lines.product_id.id,
+ "barcode": "XXX",
+ "height": 12,
+ "width": 13,
+ "packaging_length": 14,
+ }
+ )
+ )
+ response = self.service.dispatch(
+ "scan_package_action",
+ params={
+ "picking_id": picking.id,
+ "selected_line_ids": selected_lines.ids,
+ # create a new package using this packaging
+ "barcode": packaging.barcode,
+ },
+ )
+ self._assert_selected_response(
+ response,
+ selected_lines,
+ message=self.msg_store.invalid_scanned_checkout_object_wo_package(
+ "packaging",
+ ppt,
+ ),
+ allow_with_package=False,
+ )
+
+ def test_scan_package_action_scan_delivery_packaging_invalid_package_process_type(
+ self,
+ ):
+ ppt = "without_package"
+ self.menu.sudo().package_process_type = ppt
+ picking = self._create_picking(lines=[(self.product_a, 10)])
+ picking.carrier_id = picking.carrier_id.search([], limit=1)
+ pack1_moves = picking.move_lines
+ # put in 2 packs, for this test, we'll work on pack1
+ self._fill_stock_for_moves(pack1_moves, in_package=True)
+ picking.action_assign()
+ selected_lines = pack1_moves.move_line_ids
+ selected_lines.qty_done = selected_lines.product_uom_qty
+
+ packaging = (
+ self.env["product.packaging"]
+ .sudo()
+ .create(
+ {
+ "name": "DeliverX",
+ "barcode": "XXX",
+ "height": 12,
+ "width": 13,
+ "packaging_length": 14,
+ }
+ )
+ )
+ response = self.service.dispatch(
+ "scan_package_action",
+ params={
+ "picking_id": picking.id,
+ "selected_line_ids": selected_lines.ids,
+ # create a new package using this packaging
+ "barcode": packaging.barcode,
+ },
+ )
+ self._assert_selected_response(
+ response,
+ selected_lines,
+ message=self.msg_store.invalid_scanned_checkout_object_wo_package(
+ "delivery_packaging",
+ ppt,
+ ),
+ allow_with_package=False,
+ )
+
def test_scan_package_action_scan_not_found(self):
picking = self._create_picking(lines=[(self.product_a, 10)])
move = picking.move_lines
diff --git a/shopfloor/tests/test_checkout_select_line.py b/shopfloor/tests/test_checkout_select_line.py
index 1945c7c719..63c596ee00 100644
--- a/shopfloor/tests/test_checkout_select_line.py
+++ b/shopfloor/tests/test_checkout_select_line.py
@@ -31,9 +31,9 @@ def test_select_line_package_ok(self):
)
self._assert_selected(response, selected_lines)
- def test_select_line_no_package_disabled(self):
+ def test_select_line_without_package(self):
selected_lines = self.moves_pack.move_line_ids
- self.service.work.options = {"checkout__disable_no_package": True}
+ self.menu.sudo().package_process_type = "without_package"
response = self.service.dispatch(
"select_line",
params={
@@ -41,7 +41,19 @@ def test_select_line_no_package_disabled(self):
"package_id": selected_lines.package_id.id,
},
)
- self._assert_selected(response, selected_lines, no_package_enabled=False)
+ self._assert_selected(response, selected_lines, allow_with_package=False)
+
+ def test_select_line_with_package(self):
+ selected_lines = self.moves_pack.move_line_ids
+ self.menu.sudo().package_process_type = "with_package"
+ response = self.service.dispatch(
+ "select_line",
+ params={
+ "picking_id": self.picking.id,
+ "package_id": selected_lines.package_id.id,
+ },
+ )
+ self._assert_selected(response, selected_lines, allow_without_package=False)
def test_select_line_move_line_package_ok(self):
selected_lines = self.moves_pack.move_line_ids
diff --git a/shopfloor/tests/test_checkout_select_package_base.py b/shopfloor/tests/test_checkout_select_package_base.py
index a9ba7f128a..e2112f3bb7 100644
--- a/shopfloor/tests/test_checkout_select_package_base.py
+++ b/shopfloor/tests/test_checkout_select_package_base.py
@@ -9,7 +9,8 @@ def _assert_selected_response(
selected_lines,
message=None,
packing_info="",
- no_package_enabled=True,
+ allow_with_package=True,
+ allow_without_package=True,
):
picking = selected_lines.mapped("picking_id")
self.assert_response(
@@ -21,7 +22,8 @@ def _assert_selected_response(
],
"picking": self._picking_summary_data(picking),
"packing_info": packing_info,
- "no_package_enabled": no_package_enabled,
+ "allow_with_package": allow_with_package,
+ "allow_without_package": allow_without_package,
},
message=message,
)
diff --git a/shopfloor/views/shopfloor_menu.xml b/shopfloor/views/shopfloor_menu.xml
index 291862392c..55bdc15758 100644
--- a/shopfloor/views/shopfloor_menu.xml
+++ b/shopfloor/views/shopfloor_menu.xml
@@ -132,6 +132,13 @@
/>