odoo-addon-shopfloor 16.0.2.6.0__py3-none-any.whl → 16.0.2.7.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. odoo/addons/shopfloor/README.rst +1 -1
  2. odoo/addons/shopfloor/__manifest__.py +1 -1
  3. odoo/addons/shopfloor/actions/message.py +50 -5
  4. odoo/addons/shopfloor/actions/stock_unreserve.py +11 -4
  5. odoo/addons/shopfloor/i18n/ca.po +35 -13
  6. odoo/addons/shopfloor/i18n/de.po +35 -13
  7. odoo/addons/shopfloor/i18n/es_AR.po +44 -14
  8. odoo/addons/shopfloor/i18n/it.po +44 -14
  9. odoo/addons/shopfloor/i18n/pt_BR.po +35 -13
  10. odoo/addons/shopfloor/i18n/shopfloor.pot +35 -13
  11. odoo/addons/shopfloor/services/checkout.py +129 -105
  12. odoo/addons/shopfloor/services/delivery.py +89 -64
  13. odoo/addons/shopfloor/services/location_content_transfer.py +34 -18
  14. odoo/addons/shopfloor/services/service.py +52 -15
  15. odoo/addons/shopfloor/static/description/index.html +1 -1
  16. odoo/addons/shopfloor/tests/test_checkout_scan.py +11 -3
  17. odoo/addons/shopfloor/tests/test_checkout_scan_line.py +35 -4
  18. odoo/addons/shopfloor/tests/test_checkout_select.py +3 -1
  19. odoo/addons/shopfloor/tests/test_delivery_scan_deliver.py +143 -1
  20. odoo/addons/shopfloor/tests/test_delivery_set_qty_done_line.py +1 -1
  21. odoo/addons/shopfloor/tests/test_delivery_set_qty_done_pack.py +1 -1
  22. odoo/addons/shopfloor/tests/test_location_content_transfer_start.py +24 -1
  23. {odoo_addon_shopfloor-16.0.2.6.0.dist-info → odoo_addon_shopfloor-16.0.2.7.0.1.dist-info}/METADATA +2 -2
  24. {odoo_addon_shopfloor-16.0.2.6.0.dist-info → odoo_addon_shopfloor-16.0.2.7.0.1.dist-info}/RECORD +26 -26
  25. {odoo_addon_shopfloor-16.0.2.6.0.dist-info → odoo_addon_shopfloor-16.0.2.7.0.1.dist-info}/WHEEL +0 -0
  26. {odoo_addon_shopfloor-16.0.2.6.0.dist-info → odoo_addon_shopfloor-16.0.2.7.0.1.dist-info}/top_level.txt +0 -0
@@ -140,54 +140,61 @@ class Delivery(Component):
140
140
  barcode_valid = bool(picking)
141
141
 
142
142
  if picking:
143
- message = self._check_picking_status(picking)
143
+ message = self._check_picking_processible(picking)
144
144
  if message:
145
145
  return self._response_for_deliver(location=location, message=message)
146
146
 
147
147
  if picking_id:
148
148
  picking = self.env["stock.picking"].browse(picking_id)
149
149
 
150
- # Validate picking anyway
151
150
  if not barcode_valid:
152
- package = search.package_from_scan(barcode)
153
- if package:
154
- return self._deliver_package(picking, package, location)
155
-
156
- if not barcode_valid:
157
- product = search.product_from_scan(barcode)
158
- if product:
159
- return self._deliver_product(
160
- picking, product, product_qty=1, location=location
161
- )
162
-
163
- if not barcode_valid:
164
- packaging = search.packaging_from_scan(barcode)
165
- if packaging:
166
- # By scanning a packaging, we want to process
167
- # the full quantity of the packaging
168
- packaging_qty = packaging.product_uom_id._compute_quantity(
169
- packaging.qty, packaging.product_id.uom_id
170
- )
171
- return self._deliver_product(
172
- picking,
173
- packaging.product_id,
174
- product_qty=packaging_qty,
175
- location=location,
176
- )
151
+ handlers_by_type = {
152
+ "package": self._scan_deliver__by_package,
153
+ "product": self._scan_deliver__by_product,
154
+ "packaging": self._scan_deliver__by_packaging,
155
+ "lot": self._scan_deliver__by_lot,
156
+ "location": self._scan_deliver__by_location,
157
+ }
158
+ search_result = search.find(barcode, handlers_by_type.keys())
159
+ handler = handlers_by_type.get(search_result.type)
160
+ if handler:
161
+ result = handler(search_result.record, picking, location)
162
+ if result:
163
+ return result
164
+ return self._scan_deliver__fallback(picking, location, barcode_valid)
165
+
166
+ def _scan_deliver__by_package(self, package, picking, location):
167
+ return self._deliver_package(picking, package, location)
168
+
169
+ def _scan_deliver__by_product(self, product, picking, location):
170
+ return self._deliver_product(picking, product, product_qty=1, location=location)
171
+
172
+ def _scan_deliver__by_packaging(self, packaging, picking, location):
173
+ # By scanning a packaging, we want to process
174
+ # the full quantity of the packaging
175
+ packaging_qty = packaging.product_uom_id._compute_quantity(
176
+ packaging.qty, packaging.product_id.uom_id
177
+ )
178
+ return self._deliver_product(
179
+ picking,
180
+ packaging.product_id,
181
+ product_qty=packaging_qty,
182
+ location=location,
183
+ )
177
184
 
178
- if not barcode_valid:
179
- lot = search.lot_from_scan(barcode)
180
- if lot:
181
- return self._deliver_lot(picking, lot, product_qty=1, location=location)
185
+ def _scan_deliver__by_lot(self, lot, picking, location):
186
+ return self._deliver_lot(picking, lot, product_qty=1, location=location)
182
187
 
183
- if not barcode_valid:
184
- sublocation = search.location_from_scan(barcode)
185
- if sublocation and sublocation.is_sublocation_of(
186
- self.picking_types.mapped("default_location_src_id")
187
- ):
188
- message = self.msg_store.location_src_set_to_sublocation(sublocation)
189
- return self._response_for_deliver(location=sublocation, message=message)
188
+ def _scan_deliver__by_location(self, scanned_location, picking, location):
189
+ if scanned_location.is_sublocation_of(
190
+ self.picking_types.mapped("default_location_src_id")
191
+ ):
192
+ message = self.msg_store.location_src_set_to_sublocation(scanned_location)
193
+ return self._response_for_deliver(
194
+ location=scanned_location, message=message
195
+ )
190
196
 
197
+ def _scan_deliver__fallback(self, picking, location, barcode_valid):
191
198
  message = self.msg_store.barcode_not_found() if not barcode_valid else None
192
199
  return self._response_for_deliver(
193
200
  picking=picking, location=location, message=message
@@ -228,6 +235,12 @@ class Delivery(Component):
228
235
  lines = package.move_line_ids.filtered(
229
236
  lambda l: l.state in ("assigned", "partially_available")
230
237
  )
238
+ if not lines:
239
+ return self._response_for_deliver(
240
+ picking=picking,
241
+ location=location,
242
+ message=self.msg_store.cannot_move_something_in_picking_type(),
243
+ )
231
244
  # State of the picking might change while we reach this point: check again!
232
245
  message = self._check_picking_status(lines.mapped("picking_id"))
233
246
  if message:
@@ -240,12 +253,9 @@ class Delivery(Component):
240
253
  ]
241
254
  )
242
255
  return self._response_for_deliver(location=location, message=message)
243
- if not lines:
244
- return self._response_for_deliver(
245
- picking=picking,
246
- location=location,
247
- message=self.msg_store.cannot_move_something_in_picking_type(),
248
- )
256
+ message = self._check_picking_type(lines.mapped("picking_id"))
257
+ if message:
258
+ return self._response_for_deliver(location=location, message=message)
249
259
  # TODO add a message if any of the lines already had a qty_done > 0
250
260
  new_picking = fields.first(lines.mapped("picking_id"))
251
261
  if self._set_lines_done(lines):
@@ -255,12 +265,9 @@ class Delivery(Component):
255
265
  return self._response_for_deliver(picking=new_picking, location=location)
256
266
 
257
267
  def _lines_base_domain(self, no_qty_done=True):
258
- domain = [
259
- # we added auto_join for this, otherwise, the ORM would search all pickings
260
- # in the picking type, and then use IN (ids)
261
- ("picking_id.picking_type_id", "in", self.picking_types.ids),
262
- ("picking_id.state", "not in", ("done", "cancel")),
263
- ]
268
+ # we added auto_join for this, otherwise, the ORM would search all pickings
269
+ # in the picking type, and then use IN (ids)
270
+ domain = []
264
271
  if no_qty_done:
265
272
  domain.append(("qty_done", "=", 0))
266
273
  return domain
@@ -297,6 +304,16 @@ class Delivery(Component):
297
304
  )
298
305
  if location:
299
306
  domain.extend([("location_id", "=", location.id)])
307
+ else:
308
+ domain.extend(
309
+ [
310
+ (
311
+ "location_id",
312
+ "child_of",
313
+ self.picking_types.default_location_src_id.ids,
314
+ )
315
+ ]
316
+ )
300
317
  if product_qty:
301
318
  domain.extend(
302
319
  [
@@ -351,6 +368,12 @@ class Delivery(Component):
351
368
  message=self.msg_store.product_in_multiple_sublocation(product),
352
369
  )
353
370
 
371
+ message = self._check_picking_type(lines.mapped("picking_id"))
372
+ if message:
373
+ return self._response_for_deliver(location=location, message=message)
374
+ lines = lines.filtered(
375
+ lambda l: l.move_id.picking_type_id in self.picking_types
376
+ )
354
377
  # State of the picking might change while we reach this point: check again!
355
378
  message = self._check_picking_status(lines.mapped("picking_id"))
356
379
  if message:
@@ -413,15 +436,14 @@ class Delivery(Component):
413
436
  return self._response_for_deliver(new_picking, location=location)
414
437
 
415
438
  def _deliver_lot(self, picking, lot, product_qty=None, location=None):
416
- lines = self.env["stock.move.line"].search(
417
- self._lines_from_lot_domain(
418
- lot,
419
- no_qty_done=False,
420
- product_qty=product_qty,
421
- location=location,
422
- picking=picking,
423
- )
439
+ domain = self._lines_from_lot_domain(
440
+ lot,
441
+ no_qty_done=False,
442
+ product_qty=product_qty,
443
+ location=location,
444
+ picking=picking,
424
445
  )
446
+ lines = self.env["stock.move.line"].search(domain)
425
447
  if not lines:
426
448
  return self._response_for_deliver(
427
449
  picking,
@@ -439,6 +461,9 @@ class Delivery(Component):
439
461
  message=self.msg_store.lot_in_multiple_sublocation(lot),
440
462
  )
441
463
 
464
+ message = self._check_picking_type(lines.mapped("picking_id"))
465
+ if message:
466
+ return self._response_for_deliver(location=location, message=message)
442
467
  # State of the picking might change while we reach this point: check again!
443
468
  message = self._check_picking_status(lines.mapped("picking_id"))
444
469
  if message:
@@ -549,7 +574,7 @@ class Delivery(Component):
549
574
  * deliver: with information about the stock.picking
550
575
  """
551
576
  picking = self.env["stock.picking"].browse(picking_id)
552
- message = self._check_picking_status(picking)
577
+ message = self._check_picking_processible(picking)
553
578
  if message:
554
579
  return self.list_stock_picking(message=message)
555
580
  if picking:
@@ -566,7 +591,7 @@ class Delivery(Component):
566
591
  * deliver: always return here with updated data
567
592
  """
568
593
  picking = self.env["stock.picking"].browse(picking_id)
569
- message = self._check_picking_status(picking)
594
+ message = self._check_picking_processible(picking)
570
595
  if message:
571
596
  return self._response_for_deliver(message=message)
572
597
  package = self.env["stock.quant.package"].browse(package_id).exists()
@@ -591,7 +616,7 @@ class Delivery(Component):
591
616
  * deliver: always return here with updated data
592
617
  """
593
618
  picking = self.env["stock.picking"].browse(picking_id)
594
- message = self._check_picking_status(picking)
619
+ message = self._check_picking_processible(picking)
595
620
  if message:
596
621
  return self._response_for_deliver(message=message)
597
622
  line = self.env["stock.move.line"].browse(move_line_id).exists()
@@ -618,7 +643,7 @@ class Delivery(Component):
618
643
  * deliver: always return here with updated data
619
644
  """
620
645
  picking = self.env["stock.picking"].browse(picking_id)
621
- message = self._check_picking_status(picking)
646
+ message = self._check_picking_processible(picking)
622
647
  if message:
623
648
  return self._response_for_deliver(message=message)
624
649
  package = self.env["stock.quant.package"].browse(package_id).exists()
@@ -651,7 +676,7 @@ class Delivery(Component):
651
676
  * deliver: always return here with updated data
652
677
  """
653
678
  picking = self.env["stock.picking"].browse(picking_id)
654
- message = self._check_picking_status(picking)
679
+ message = self._check_picking_processible(picking)
655
680
  if message:
656
681
  return self._response_for_deliver(message=message)
657
682
  line = self.env["stock.move.line"].browse(move_line_id).exists()
@@ -681,7 +706,7 @@ class Delivery(Component):
681
706
  * confirm_done: when not all lines of the stock.picking are done
682
707
  """
683
708
  picking = self.env["stock.picking"].browse(picking_id)
684
- message = self._check_picking_status(picking)
709
+ message = self._check_picking_processible(picking)
685
710
  if message:
686
711
  return self._response_for_deliver(message=message)
687
712
  if self._action_picking_done(picking):
@@ -342,7 +342,9 @@ class LocationContentTransfer(Component):
342
342
 
343
343
  unreserved_moves = self.env["stock.move"].browse()
344
344
  if self.work.menu.allow_unreserve_other_moves:
345
- message = unreserve.check_unreserve(location, move_lines)
345
+ message = unreserve.check_unreserve(
346
+ location, move_lines, allowed_types=self.picking_types
347
+ )
346
348
  if message:
347
349
  return self._response_for_start(message=message)
348
350
  move_lines, unreserved_moves = unreserve.unreserve_moves(
@@ -602,20 +604,31 @@ class LocationContentTransfer(Component):
602
604
  )
603
605
 
604
606
  search = self._actions_for("search")
607
+ handlers = {
608
+ "package": self._scan_line__by_package,
609
+ "product": self._scan_line__by_product,
610
+ "packaging": self._scan_line__by_packaging,
611
+ "lot": self._scan_line__by_lot,
612
+ "none": self._scan_line__fallback,
613
+ }
614
+ search_result = search.find(barcode, types=handlers.keys())
615
+ handler = handlers.get(search_result.type, self._scan_line__fallback)
616
+ # handler might've been called but returned no response.
617
+ # I.E. package is scanned but doesn't matches move_line's package.
618
+ # Call explicitely fallback in such case
619
+ response = handler(search_result.record, move_line, location)
620
+ return response or self._scan_line__fallback(
621
+ search_result.record, move_line, location
622
+ )
605
623
 
606
- package = search.package_from_scan(barcode)
607
- if package and move_line.package_id == package:
624
+ def _scan_line__by_package(self, package, move_line, location):
625
+ if move_line.package_id == package:
608
626
  # In case we have a source package but no package level because if
609
627
  # we have a package level, we would use "scan_package".
610
628
  return self._response_for_scan_destination(location, move_line)
611
629
 
612
- product = search.product_from_scan(barcode)
613
- if not product:
614
- packaging = search.packaging_from_scan(barcode)
615
- if packaging:
616
- product = packaging.product_id
617
-
618
- if product and product == move_line.product_id:
630
+ def _scan_line__by_product(self, product, move_line, location):
631
+ if product == move_line.product_id:
619
632
  if product.tracking in ("lot", "serial"):
620
633
  move_lines = self._find_transfer_move_lines(location)
621
634
  return self._response_for_start_single(
@@ -625,18 +638,21 @@ class LocationContentTransfer(Component):
625
638
  else:
626
639
  return self._response_for_scan_destination(location, move_line)
627
640
 
628
- lot = search.lot_from_scan(barcode, products=move_line.product_id)
629
- if lot and lot == move_line.lot_id:
641
+ def _scan_line__by_packaging(self, packaging, move_line, location):
642
+ return self._scan_line__by_product(packaging.product_id, move_line, location)
643
+
644
+ def _scan_line__by_lot(self, lot, move_line, location):
645
+ if lot == move_line.lot_id:
630
646
  return self._response_for_scan_destination(location, move_line)
631
647
 
648
+ def _scan_line__fallback(self, record, move_line, location):
632
649
  # Nothing matches what is expected from the move line.
633
650
  move_lines = self._find_transfer_move_lines(location)
634
- for rec in (package, product, lot):
635
- if rec:
636
- return self._response_for_start_single(
637
- move_lines.mapped("picking_id"),
638
- message=self.msg_store.wrong_record(rec),
639
- )
651
+ if record:
652
+ return self._response_for_start_single(
653
+ move_lines.mapped("picking_id"),
654
+ message=self.msg_store.wrong_record(record),
655
+ )
640
656
  return self._response_for_start_single(
641
657
  move_lines.mapped("picking_id"), message=self.msg_store.barcode_not_found()
642
658
  )
@@ -2,7 +2,8 @@
2
2
  # Copyright 2020 Akretion (http://www.akretion.com)
3
3
  # Copyright 2020-2021 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
4
4
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
5
- from odoo import _, exceptions
5
+ from odoo import _, exceptions, fields
6
+ from odoo.osv.expression import AND
6
7
 
7
8
  from odoo.addons.component.core import AbstractComponent
8
9
 
@@ -34,6 +35,22 @@ class BaseShopfloorProcess(AbstractComponent):
34
35
  """Return picking types for the menu"""
35
36
  return self.work.menu.picking_type_ids
36
37
 
38
+ def _get_pickings_base_domain(self):
39
+ return [
40
+ ("state", "not in", ("done", "cancel")),
41
+ ("location_id", "child_of", self.picking_types.default_location_src_id.ids),
42
+ ]
43
+
44
+ def _get_pickings_for_package(self, package, **kwargs):
45
+ domain = self._get_pickings_base_domain()
46
+ package_domain = [("move_line_ids.package_id", "=", package.id)]
47
+ return self.env["stock.picking"].search(AND([domain, package_domain]), **kwargs)
48
+
49
+ def _get_pickings_for_product(self, product, **kwargs):
50
+ domain = self._get_pickings_base_domain()
51
+ product_domain = [("move_line_ids.product_id", "=", product.id)]
52
+ return self.env["stock.picking"].search(AND([domain, product_domain]), **kwargs)
53
+
37
54
  @property
38
55
  def picking_types(self):
39
56
  if not hasattr(self.work, "picking_types"):
@@ -72,21 +89,41 @@ class BaseShopfloorProcess(AbstractComponent):
72
89
  sort_order_custom_code=self.sort_order_custom_code,
73
90
  )
74
91
 
75
- def _check_picking_status(self, pickings, states=("assigned",)):
76
- """Check if given pickings can be processed.
92
+ def _check_picking_consistency(self, pickings):
93
+ if not pickings.exists():
94
+ return self.msg_store.stock_picking_not_found()
95
+
96
+ def _check_picking_type(self, pickings):
97
+ """Check if the pickings have the right expected type."""
98
+ if not any(
99
+ picking.picking_type_id in self.picking_types for picking in pickings
100
+ ):
101
+ return self.msg_store.reserved_for_other_picking_type(
102
+ fields.first(pickings)
103
+ )
77
104
 
78
- If the picking is already done, canceled or didn't belong to the
79
- expected picking type, a message is returned.
80
- """
81
- for picking in pickings:
82
- if not picking.exists():
83
- return self.msg_store.stock_picking_not_found()
84
- if picking.state == "done":
85
- return self.msg_store.already_done()
86
- if picking.state not in states: # the picking must be ready
87
- return self.msg_store.stock_picking_not_available(picking)
88
- if picking.picking_type_id not in self.picking_types:
89
- return self.msg_store.cannot_move_something_in_picking_type()
105
+ def _check_picking_status(self, pickings, states=("assigned",)):
106
+ """Checks if the picking exists, is already done or canceled."""
107
+ if not any(picking.state != "done" for picking in pickings):
108
+ return self.msg_store.already_done()
109
+ if not any(picking.state != "cancel" for picking in pickings):
110
+ return self.msg_store.transfer_canceled()
111
+ if not any(
112
+ picking.state in states for picking in pickings
113
+ ): # the picking must be ready
114
+ return self.msg_store.stock_picking_not_available(fields.first(pickings))
115
+
116
+ def _check_picking_processible(self, pickings, states=("assigned",)):
117
+ """Check if given pickings can be processed"""
118
+ message = self._check_picking_consistency(pickings)
119
+ if message:
120
+ return message
121
+ message = self._check_picking_type(pickings)
122
+ if message:
123
+ return message
124
+ message = self._check_picking_status(pickings, states=states)
125
+ if message:
126
+ return message
90
127
 
91
128
  def is_src_location_valid(self, location):
92
129
  """Check the source location is valid for given process.
@@ -367,7 +367,7 @@ ul.auto-toc {
367
367
  !! This file is generated by oca-gen-addon-readme !!
368
368
  !! changes will be overwritten. !!
369
369
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
370
- !! source digest: sha256:15590a835f281c13f0eae031c50d19d04ab77e94ee95b697d5eabd04373c21ce
370
+ !! source digest: sha256:986a6c228f6ee438330907a4be64b74f7291bb1acfc6498a2617ed5e69777d9e
371
371
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
372
372
  <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/wms/tree/16.0/shopfloor"><img alt="OCA/wms" src="https://img.shields.io/badge/github-OCA%2Fwms-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/wms-16-0/wms-16-0-shopfloor"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/wms&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
373
373
  <p>Shopfloor is a barcode scanner application for internal warehouse operations.</p>
@@ -41,7 +41,10 @@ class CheckoutScanCase(CheckoutCommonCase):
41
41
  self.assert_response(
42
42
  response,
43
43
  next_state="select_document",
44
- message={"message_type": "error", "body": "Barcode not found"},
44
+ message={
45
+ "message_type": "error",
46
+ "body": "No transfer found for barcode A",
47
+ },
45
48
  data={"restrict_scan_first": True},
46
49
  )
47
50
 
@@ -56,7 +59,10 @@ class CheckoutScanCase(CheckoutCommonCase):
56
59
  self.assert_response(
57
60
  response,
58
61
  next_state="select_document",
59
- message={"message_type": "error", "body": "Barcode not found"},
62
+ message={
63
+ "message_type": "error",
64
+ "body": "No transfer found for barcode NOPE",
65
+ },
60
66
  data={"restrict_scan_first": False},
61
67
  )
62
68
 
@@ -117,12 +123,14 @@ class CheckoutScanCase(CheckoutCommonCase):
117
123
  picking.action_assign()
118
124
  barcode = barcode_func(picking)
119
125
  response = self.service.dispatch("scan_document", params={"barcode": barcode})
126
+ picking_name = picking.name
127
+ type_name = picking.picking_type_id.name
120
128
  self.assert_response(
121
129
  response,
122
130
  next_state="select_document",
123
131
  message={
124
132
  "message_type": "error",
125
- "body": "You cannot move this using this menu.",
133
+ "body": f"Reserved for {type_name} {picking_name}",
126
134
  },
127
135
  data={"restrict_scan_first": False},
128
136
  )
@@ -164,6 +164,24 @@ class CheckoutScanLineCase(CheckoutScanLineCaseBase):
164
164
  )
165
165
 
166
166
  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_ids, 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):
167
185
  picking = self._create_picking(lines=[(self.product_a, 10)])
168
186
  self._fill_stock_for_moves(picking.move_ids, in_package=True)
169
187
  picking2 = self._create_picking(lines=[(self.product_a, 10)])
@@ -176,9 +194,7 @@ class CheckoutScanLineCase(CheckoutScanLineCaseBase):
176
194
  package.name,
177
195
  {
178
196
  "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}",
182
198
  },
183
199
  )
184
200
 
@@ -247,7 +263,22 @@ class CheckoutScanLineCase(CheckoutScanLineCaseBase):
247
263
  self.product_b.barcode,
248
264
  {
249
265
  "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_ids, in_package=True)
273
+ picking2 = self._create_picking(lines=[(self.product_b, 10)])
274
+ self._fill_stock_for_moves(picking2.move_ids, 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}",
251
282
  },
252
283
  )
253
284
 
@@ -68,7 +68,9 @@ class CheckoutSelectCase(CheckoutCommonCase):
68
68
  )
69
69
 
70
70
  def test_select_error_not_allowed(self):
71
+ # Trying to pick a picking with wrong picking type
71
72
  picking = self._create_picking(picking_type=self.wh.pick_type_id)
72
73
  self._fill_stock_for_moves(picking.move_ids, in_package=True)
73
74
  picking.action_assign()
74
- self._test_error(picking, "You cannot move this using this menu.")
75
+ expected_message = f"Reserved for {picking.picking_type_id.name} {picking.name}"
76
+ self._test_error(picking, expected_message)