Skip to content

Commit d04a263

Browse files
committed
shopfloor checkout: scan line better messages
1 parent 704ada6 commit d04a263

File tree

7 files changed

+134
-57
lines changed

7 files changed

+134
-57
lines changed

shopfloor/actions/message.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -560,11 +560,9 @@ def place_in_location_ask_confirmation(self, location_name):
560560
"body": _("Place it in {}?").format(location_name),
561561
}
562562

563-
def product_not_found_in_current_picking(self):
564-
return {
565-
"message_type": "error",
566-
"body": _("Product is not in the current transfer."),
567-
}
563+
def product_not_found_in_current_picking(self, product):
564+
body = _("Product {} is not in the current transfer.").format(product.name)
565+
return {"message_type": "error", "body": body}
568566

569567
def lot_mixed_package_scan_package(self):
570568
return {

shopfloor/services/checkout.py

+52-41
Original file line numberDiff line numberDiff line change
@@ -471,21 +471,31 @@ def scan_line(self, picking_id, barcode, confirm_pack_all=False, confirm_lot=Non
471471
return self._response_for_summary(picking)
472472

473473
# Search of the destination package
474-
search_result = self._scan_line_find(picking, barcode)
475-
result_handler = getattr(self, "_select_lines_from_" + search_result.type)
476-
kw = {"confirm_pack_all": confirm_pack_all, "confirm_lot": confirm_lot}
477-
return result_handler(picking, selection_lines, search_result.record, **kw)
474+
handlers = {
475+
"package": self._select_lines_from_package,
476+
"product": self._select_lines_from_product,
477+
"packaging": self._select_lines_from_packaging,
478+
"lot": self._select_lines_from_lot,
479+
"serial": self._select_lines_from_serial,
480+
"delivery_packaging": self._select_lines_from_delivery_packaging,
481+
"none": self._select_lines_from_none,
482+
}
483+
search_result = self._scan_line_find(picking, barcode, handlers.keys())
484+
# setting scanned record as kwarg in order to make better logs.
485+
# The reason for this is that from a product we might select various records
486+
# and lose track of what was initially scanned. This forces us to display
487+
# standard messages that might have no meaning for the user.
488+
kwargs = {
489+
"confirm_pack_all": confirm_pack_all,
490+
"confirm_lot": confirm_lot,
491+
"scanned_record": search_result.record,
492+
"barcode": barcode,
493+
}
494+
handler = handlers.get(search_result.type, self._select_lines_from_none)
495+
return handler(picking, selection_lines, search_result.record, **kwargs)
478496

479-
def _scan_line_find(self, picking, barcode, search_types=None):
497+
def _scan_line_find(self, picking, barcode, search_types):
480498
search = self._actions_for("search")
481-
search_types = (
482-
"package",
483-
"product",
484-
"packaging",
485-
"lot",
486-
"serial",
487-
"delivery_packaging",
488-
)
489499
return search.find(
490500
barcode,
491501
types=search_types,
@@ -508,15 +518,14 @@ def _select_lines_from_package(
508518
lambda l: l.package_id == package and not l.shopfloor_checkout_done
509519
)
510520
if not lines:
511-
return self._response_for_select_line(
512-
picking,
513-
message={
514-
"message_type": "error",
515-
"body": _("Package {} is not in the current transfer.").format(
516-
package.name
517-
),
518-
},
519-
)
521+
# No line for scanned package in selected picking
522+
# Check if there's any picking reserving this product.
523+
return_picking = self._get_pickings_for_package(package, limit=1)
524+
if return_picking:
525+
message = self.msg_store.reserved_for_other_picking_type(return_picking)
526+
else:
527+
message = self.msg_store.package_not_found_in_picking(package, picking)
528+
return self._response_for_select_line(picking, message=message)
520529
self._select_lines(lines, prefill_qty=prefill_qty)
521530
if self.work.menu.no_prefill_qty:
522531
lines = picking.move_line_ids
@@ -533,9 +542,12 @@ def _select_lines_from_product(
533542

534543
lines = selection_lines.filtered(lambda l: l.product_id == product)
535544
if not lines:
536-
return self._response_for_select_line(
537-
picking, message=self.msg_store.product_not_found_in_current_picking()
538-
)
545+
return_picking = self._get_pickings_for_product(product, limit=1)
546+
if return_picking:
547+
message = self.msg_store.reserved_for_other_picking_type(return_picking)
548+
else:
549+
message = self.msg_store.product_not_found_in_current_picking(product)
550+
return self._response_for_select_line(picking, message=message)
539551

540552
# When products are as units outside of packages, we can select them for
541553
# packing, but if they are in a package, we want the user to scan the packages.
@@ -1047,18 +1059,21 @@ def scan_package_action(self, picking_id, selected_line_ids, barcode):
10471059
return self._response_for_select_document(message=message)
10481060

10491061
selected_lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
1050-
search_result = self._scan_package_find(picking, barcode)
1051-
message = self._check_scan_package_find(picking, search_result)
1052-
if message:
1053-
return self._response_for_select_package(
1054-
picking,
1055-
selected_lines,
1056-
message=message,
1057-
)
1058-
result_handler = getattr(
1059-
self, "_scan_package_action_from_" + search_result.type
1060-
)
1061-
return result_handler(picking, selected_lines, search_result.record)
1062+
handlers = {
1063+
"package": self._scan_package_action_from_package,
1064+
"product": self._scan_package_action_from_product,
1065+
"packaging": self._scan_package_action_from_packaging,
1066+
"lot": self._scan_package_action_from_lot,
1067+
"serial": self._scan_package_action_from_serial,
1068+
"delivery_packaging": self._scan_package_action_from_delivery_packaging,
1069+
}
1070+
search_result = self._scan_package_find(picking, barcode, handlers.keys())
1071+
handler = handlers.get(search_result.type, self._scan_package_action_from_none)
1072+
kwargs = {
1073+
"barcode": barcode,
1074+
"scanned_record": search_result.record,
1075+
}
1076+
return handler(picking, selected_lines, search_result.record, **kwargs)
10621077

10631078
def _scan_package_find(self, picking, barcode, search_types=None):
10641079
search = self._actions_for("search")
@@ -1079,10 +1094,6 @@ def _scan_package_find(self, picking, barcode, search_types=None):
10791094
),
10801095
)
10811096

1082-
def _check_scan_package_find(self, picking, search_result):
1083-
# Used by inheriting modules
1084-
return False
1085-
10861097
def _find_line_to_increment(self, product_lines):
10871098
"""Find which line should have its qty incremented.
10881099

shopfloor/services/service.py

+17
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Copyright 2020-2021 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
44
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
55
from odoo import _, exceptions, fields
6+
from odoo.osv.expression import AND
67

78
from odoo.addons.component.core import AbstractComponent
89

@@ -26,6 +27,22 @@ def _get_process_picking_types(self):
2627
"""Return picking types for the menu"""
2728
return self.work.menu.picking_type_ids
2829

30+
def _get_pickings_base_domain(self):
31+
return [
32+
("state", "not in", ("done", "cancel")),
33+
("location_id", "child_of", self.picking_types.default_location_src_id.ids),
34+
]
35+
36+
def _get_pickings_for_package(self, package, **kwargs):
37+
domain = self._get_pickings_base_domain()
38+
package_domain = [("move_line_ids.package_id", "=", package.id)]
39+
return self.env["stock.picking"].search(AND([domain, package_domain]), **kwargs)
40+
41+
def _get_pickings_for_product(self, product, **kwargs):
42+
domain = self._get_pickings_base_domain()
43+
product_domain = [("move_line_ids.product_id", "=", product.id)]
44+
return self.env["stock.picking"].search(AND([domain, product_domain]), **kwargs)
45+
2946
@property
3047
def picking_types(self):
3148
if not hasattr(self.work, "picking_types"):

shopfloor/tests/test_checkout_scan_line.py

+35-4
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,24 @@ def test_scan_line_error_barcode_not_found(self):
164164
)
165165

166166
def test_scan_line_error_package_not_in_picking(self):
167+
picking = self._create_picking(lines=[(self.product_a, 10)])
168+
self._fill_stock_for_moves(picking.move_lines, in_package=True)
169+
picking.action_assign()
170+
# Create a package for product_a
171+
package = self._create_package_in_location(
172+
picking.location_id, [(self.product_a, 10, None)]
173+
)
174+
# we work with picking, but we scan another package (not in a pick)
175+
self._test_scan_line_error(
176+
picking,
177+
package.name,
178+
{
179+
"message_type": "error",
180+
"body": f"Package {package.name} not found in transfer {picking.name}",
181+
},
182+
)
183+
184+
def test_scan_line_error_package_reserved_by_another_picking(self):
167185
picking = self._create_picking(lines=[(self.product_a, 10)])
168186
self._fill_stock_for_moves(picking.move_lines, in_package=True)
169187
picking2 = self._create_picking(lines=[(self.product_a, 10)])
@@ -176,9 +194,7 @@ def test_scan_line_error_package_not_in_picking(self):
176194
package.name,
177195
{
178196
"message_type": "error",
179-
"body": "Package {} is not in the current transfer.".format(
180-
package.name
181-
),
197+
"body": f"Reserved for Checkout {picking2.name}",
182198
},
183199
)
184200

@@ -247,7 +263,22 @@ def test_scan_line_error_product_not_in_picking(self):
247263
self.product_b.barcode,
248264
{
249265
"message_type": "error",
250-
"body": "Product is not in the current transfer.",
266+
"body": "Product Product B is not in the current transfer.",
267+
},
268+
)
269+
270+
def test_scan_line_error_product_in_another_picking(self):
271+
picking = self._create_picking(lines=[(self.product_a, 10)])
272+
self._fill_stock_for_moves(picking.move_lines, in_package=True)
273+
picking2 = self._create_picking(lines=[(self.product_b, 10)])
274+
self._fill_stock_for_moves(picking2.move_lines, in_package=True)
275+
(picking | picking2).action_assign()
276+
self._test_scan_line_error(
277+
picking,
278+
self.product_b.barcode,
279+
{
280+
"message_type": "error",
281+
"body": f"Reserved for Checkout {picking2.name}",
251282
},
252283
)
253284

shopfloor_checkout_putinpack_restriction/services/checkout.py

+25-5
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,28 @@ def _data_response_for_select_package(self, picking, lines, message=None):
1616
res["no_package_enabled"] = False
1717
return res
1818

19-
def _check_scan_package_find(self, picking, search_result):
20-
if search_result.type in ["package", "delivery_packaging"]:
21-
if picking.put_in_pack_restriction == "no_package":
22-
return self.msg_store.package_not_allowed_for_operation()
23-
return super()._check_scan_package_find(picking, search_result)
19+
def _scan_package_action_from_package(
20+
self, picking, selected_lines, record, **kwargs
21+
):
22+
if picking.put_in_pack_restriction == "no_package":
23+
return self._response_for_select_package(
24+
picking,
25+
selected_lines,
26+
message=self.msg_store.package_not_allowed_for_operation(),
27+
)
28+
return super()._scan_package_action_from_package(
29+
picking, selected_lines, record, **kwargs
30+
)
31+
32+
def _scan_package_action_from_delivery_packaging(
33+
self, picking, selected_lines, record, **kwargs
34+
):
35+
if picking.put_in_pack_restriction == "no_package":
36+
return self._response_for_select_package(
37+
picking,
38+
selected_lines,
39+
message=self.msg_store.package_not_allowed_for_operation(),
40+
)
41+
return super()._scan_package_action_from_delivery_packaging(
42+
picking, selected_lines, record, **kwargs
43+
)

shopfloor_reception/services/reception.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ def _scan_line__by_product__return(self, picking, product):
412412
# If we have an origin picking but no origin move, then user
413413
# scanned a wrong product. Warn him about this.
414414
if origin_moves and not origin_moves_for_product:
415-
message = self.msg_store.product_not_found_in_current_picking()
415+
message = self.msg_store.product_not_found_in_current_picking(product)
416416
return self._response_for_select_move(picking, message=message)
417417
if origin_moves_for_product:
418418
return_move = self._scan_line__create_return_move(

shopfloor_reception/tests/test_return_scan_line.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_scan_product_not_in_delivery(self):
2222
data={"picking": self._data_for_picking_with_moves(return_picking)},
2323
message={
2424
"message_type": "error",
25-
"body": "Product is not in the current transfer.",
25+
"body": f"Product {wrong_product.name} is not in the current transfer.",
2626
},
2727
)
2828

0 commit comments

Comments
 (0)