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
@@ -214,25 +214,29 @@ class Checkout(Component):
214
214
  * summary: stock.picking is selected and all its lines have a
215
215
  destination pack set
216
216
  """
217
- search_result = self._scan_document_find(barcode)
218
- result_handler = getattr(self, "_select_document_from_" + search_result.type)
219
- return result_handler(search_result.record)
217
+ handlers = {
218
+ "picking": self._select_document_from_picking,
219
+ "location": self._select_document_from_location,
220
+ "package": self._select_document_from_package,
221
+ "packaging": self._select_document_from_packaging,
222
+ "product": self._select_document_from_product,
223
+ "none": self._select_document_from_none,
224
+ }
225
+ if self.work.menu.scan_location_or_pack_first:
226
+ handlers.pop("product")
227
+ search_result = self._scan_document_find(barcode, handlers.keys())
228
+ # Keep track of what has been initially scan, and forward it through kwargs
229
+ kwargs = {
230
+ "barcode": barcode,
231
+ "current_state": "select_document",
232
+ "scanned_record": search_result.record,
233
+ }
234
+ handler = handlers.get(search_result.type, self._select_document_from_none)
235
+ return handler(search_result.record, **kwargs)
220
236
 
221
- def _scan_document_find(self, barcode, search_types=None):
237
+ def _scan_document_find(self, barcode, search_types):
222
238
  search = self._actions_for("search")
223
- search_types = (
224
- "picking",
225
- "location",
226
- "package",
227
- "packaging",
228
- ) + (("product",) if not self.work.menu.scan_location_or_pack_first else ())
229
- return search.find(
230
- barcode,
231
- types=search_types,
232
- )
233
-
234
- def _select_document_from_picking(self, picking, **kw):
235
- return self._select_picking(picking, "select_document")
239
+ return search.find(barcode, types=search_types)
236
240
 
237
241
  def _select_document_from_location(self, location, **kw):
238
242
  if not self.is_src_location_valid(location):
@@ -251,7 +255,9 @@ class Checkout(Component):
251
255
  ),
252
256
  }
253
257
  )
254
- return self._select_picking(pickings, "select_document")
258
+ # Keep track of what has been initially scan, and forward it through kwargs
259
+ kwargs = {**kw, "current_state": "select_document"}
260
+ return self._select_document_from_picking(pickings, **kwargs)
255
261
 
256
262
  def _select_document_from_package(self, package, **kw):
257
263
  pickings = package.move_line_ids.filtered(
@@ -260,14 +266,15 @@ class Checkout(Component):
260
266
  if len(pickings) > 1:
261
267
  # Filter only if we find several pickings to narrow the
262
268
  # selection to one of the good type. If we have one picking
263
- # of the wrong type, it will be caught in _select_picking
269
+ # of the wrong type, it will be caught in _select_document_from_picking
264
270
  # with the proper error message.
265
271
  # Side note: rather unlikely to have several transfers ready
266
272
  # and moving the same things
267
273
  pickings = pickings.filtered(
268
274
  lambda p: p.picking_type_id in self.picking_types
269
275
  )
270
- return self._select_picking(fields.first(pickings), "select_document")
276
+ kwargs = {**kw, "current_state": "select_document"}
277
+ return self._select_document_from_picking(fields.first(pickings), **kwargs)
271
278
 
272
279
  def _select_document_from_product(self, product, line_domain=None, **kw):
273
280
  line_domain = line_domain or []
@@ -287,7 +294,8 @@ class Checkout(Component):
287
294
  order="priority desc, scheduled_date asc, id desc",
288
295
  limit=1,
289
296
  )
290
- return self._select_picking(picking, "select_document")
297
+ kwargs = {**kw, "current_state": "select_document"}
298
+ return self._select_document_from_picking(picking, **kwargs)
291
299
 
292
300
  def _select_document_from_packaging(self, packaging, **kw):
293
301
  # And retrieve its product
@@ -298,35 +306,33 @@ class Checkout(Component):
298
306
  line_domain = [("reserved_uom_qty", ">=", packaging.qty)]
299
307
  return self._select_document_from_product(product, line_domain=line_domain)
300
308
 
301
- def _select_document_from_none(self, picking, **kw):
309
+ def _select_document_from_none(self, *args, barcode=None, **kwargs):
302
310
  """Handle result when no record is found."""
303
- return self._select_picking(picking, "select_document")
311
+ return self._response_for_select_document(
312
+ message=self.msg_store.transfer_not_found_for_barcode(barcode)
313
+ )
304
314
 
305
- def _select_picking(self, picking, state_for_error):
315
+ def _select_document_from_picking(
316
+ self, picking, current_state=None, barcode=None, **kwargs
317
+ ):
318
+ # Get origin record to give more context to the user when raising an error
319
+ # as we got picking from product/package/packaging/...
320
+ scanned_record = kwargs.get("scanned_record")
306
321
  if not picking:
307
- if state_for_error == "manual_selection":
308
- return self._response_for_manual_selection(
309
- message=self.msg_store.stock_picking_not_found()
310
- )
311
- return self._response_for_select_document(
312
- message=self.msg_store.barcode_not_found()
313
- )
322
+ message = self.msg_store.transfer_not_found_for_record(scanned_record)
323
+ if current_state == "manual_selection":
324
+ return self._response_for_manual_selection(message=message)
325
+ return self._response_for_select_document(message=message)
314
326
  if picking.picking_type_id not in self.picking_types:
315
- if state_for_error == "manual_selection":
316
- return self._response_for_manual_selection(
317
- message=self.msg_store.cannot_move_something_in_picking_type()
318
- )
319
- return self._response_for_select_document(
320
- message=self.msg_store.cannot_move_something_in_picking_type()
321
- )
327
+ message = self.msg_store.reserved_for_other_picking_type(picking)
328
+ if current_state == "manual_selection":
329
+ return self._response_for_manual_selection(message=message)
330
+ return self._response_for_select_document(message=message)
322
331
  if picking.state != "assigned":
323
- if state_for_error == "manual_selection":
324
- return self._response_for_manual_selection(
325
- message=self.msg_store.stock_picking_not_available(picking)
326
- )
327
- return self._response_for_select_document(
328
- message=self.msg_store.stock_picking_not_available(picking)
329
- )
332
+ message = self.msg_store.stock_picking_not_available(picking)
333
+ if current_state == "manual_selection":
334
+ return self._response_for_manual_selection(message=message)
335
+ return self._response_for_select_document(message=message)
330
336
  return self._response_for_select_line(picking)
331
337
 
332
338
  def _data_for_move_lines(self, lines, **kw):
@@ -402,10 +408,17 @@ class Checkout(Component):
402
408
  lines
403
409
  """
404
410
  picking = self.env["stock.picking"].browse(picking_id)
405
- message = self._check_picking_status(picking)
411
+ message = self._check_picking_processible(picking)
406
412
  if message:
407
413
  return self._response_for_manual_selection(message=message)
408
- return self._select_picking(picking, "manual_selection")
414
+ # Because _select_document_from_picking expects some context
415
+ # to give meaningful infos to the user, add some here.
416
+ kwargs = {
417
+ "current_state": "manual_selection",
418
+ "barcode": picking.name,
419
+ "scanned_record": picking,
420
+ }
421
+ return self._select_document_from_picking(picking, **kwargs)
409
422
 
410
423
  def _select_lines(self, lines, prefill_qty=0, related_lines=None):
411
424
  for i, line in enumerate(lines):
@@ -451,7 +464,7 @@ class Checkout(Component):
451
464
  screen to change the qty done and destination pack if needed
452
465
  """
453
466
  picking = self.env["stock.picking"].browse(picking_id)
454
- message = self._check_picking_status(picking)
467
+ message = self._check_picking_processible(picking)
455
468
  if message:
456
469
  return self._response_for_select_document(message=message)
457
470
 
@@ -460,21 +473,31 @@ class Checkout(Component):
460
473
  return self._response_for_summary(picking)
461
474
 
462
475
  # Search of the destination package
463
- search_result = self._scan_line_find(picking, barcode)
464
- result_handler = getattr(self, "_select_lines_from_" + search_result.type)
465
- kw = {"confirm_pack_all": confirm_pack_all, "confirm_lot": confirm_lot}
466
- return result_handler(picking, selection_lines, search_result.record, **kw)
476
+ handlers = {
477
+ "package": self._select_lines_from_package,
478
+ "product": self._select_lines_from_product,
479
+ "packaging": self._select_lines_from_packaging,
480
+ "lot": self._select_lines_from_lot,
481
+ "serial": self._select_lines_from_serial,
482
+ "delivery_packaging": self._select_lines_from_delivery_packaging,
483
+ "none": self._select_lines_from_none,
484
+ }
485
+ search_result = self._scan_line_find(picking, barcode, handlers.keys())
486
+ # setting scanned record as kwarg in order to make better logs.
487
+ # The reason for this is that from a product we might select various records
488
+ # and lose track of what was initially scanned. This forces us to display
489
+ # standard messages that might have no meaning for the user.
490
+ kwargs = {
491
+ "confirm_pack_all": confirm_pack_all,
492
+ "confirm_lot": confirm_lot,
493
+ "scanned_record": search_result.record,
494
+ "barcode": barcode,
495
+ }
496
+ handler = handlers.get(search_result.type, self._select_lines_from_none)
497
+ return handler(picking, selection_lines, search_result.record, **kwargs)
467
498
 
468
- def _scan_line_find(self, picking, barcode, search_types=None):
499
+ def _scan_line_find(self, picking, barcode, search_types):
469
500
  search = self._actions_for("search")
470
- search_types = (
471
- "package",
472
- "product",
473
- "packaging",
474
- "lot",
475
- "serial",
476
- "delivery_packaging",
477
- )
478
501
  return search.find(
479
502
  barcode,
480
503
  types=search_types,
@@ -497,15 +520,14 @@ class Checkout(Component):
497
520
  lambda l: l.package_id == package and not l.shopfloor_checkout_done
498
521
  )
499
522
  if not lines:
500
- return self._response_for_select_line(
501
- picking,
502
- message={
503
- "message_type": "error",
504
- "body": _("Package {} is not in the current transfer.").format(
505
- package.name
506
- ),
507
- },
508
- )
523
+ # No line for scanned package in selected picking
524
+ # Check if there's any picking reserving this product.
525
+ return_picking = self._get_pickings_for_package(package, limit=1)
526
+ if return_picking:
527
+ message = self.msg_store.reserved_for_other_picking_type(return_picking)
528
+ else:
529
+ message = self.msg_store.package_not_found_in_picking(package, picking)
530
+ return self._response_for_select_line(picking, message=message)
509
531
  self._select_lines(lines, prefill_qty=prefill_qty)
510
532
  if self.work.menu.no_prefill_qty:
511
533
  lines = picking.move_line_ids
@@ -522,9 +544,12 @@ class Checkout(Component):
522
544
 
523
545
  lines = selection_lines.filtered(lambda l: l.product_id == product)
524
546
  if not lines:
525
- return self._response_for_select_line(
526
- picking, message=self.msg_store.product_not_found_in_current_picking()
527
- )
547
+ return_picking = self._get_pickings_for_product(product, limit=1)
548
+ if return_picking:
549
+ message = self.msg_store.reserved_for_other_picking_type(return_picking)
550
+ else:
551
+ message = self.msg_store.product_not_found_in_current_picking(product)
552
+ return self._response_for_select_line(picking, message=message)
528
553
 
529
554
  # When products are as units outside of packages, we can select them for
530
555
  # packing, but if they are in a package, we want the user to scan the packages.
@@ -774,7 +799,7 @@ class Checkout(Component):
774
799
  assert package_id or move_line_id
775
800
 
776
801
  picking = self.env["stock.picking"].browse(picking_id)
777
- message = self._check_picking_status(picking)
802
+ message = self._check_picking_processible(picking)
778
803
  if message:
779
804
  return self._response_for_select_document(message=message)
780
805
 
@@ -793,7 +818,7 @@ class Checkout(Component):
793
818
  self, picking_id, selected_line_ids, move_line_ids, quantity_func
794
819
  ):
795
820
  picking = self.env["stock.picking"].browse(picking_id)
796
- message = self._check_picking_status(picking)
821
+ message = self._check_picking_processible(picking)
797
822
  if message:
798
823
  return self._response_for_select_document(message=message)
799
824
 
@@ -1031,25 +1056,28 @@ class Checkout(Component):
1031
1056
  to close the stock picking
1032
1057
  """
1033
1058
  picking = self.env["stock.picking"].browse(picking_id)
1034
- message = self._check_picking_status(picking)
1059
+ message = self._check_picking_processible(picking)
1035
1060
  if message:
1036
1061
  return self._response_for_select_document(message=message)
1037
1062
 
1038
1063
  selected_lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
1039
- search_result = self._scan_package_find(picking, barcode)
1040
- message = self._check_scan_package_find(picking, search_result)
1041
- if message:
1042
- return self._response_for_select_package(
1043
- picking,
1044
- selected_lines,
1045
- message=message,
1046
- )
1047
- result_handler = getattr(
1048
- self, "_scan_package_action_from_" + search_result.type
1049
- )
1050
- return result_handler(picking, selected_lines, search_result.record)
1064
+ handlers = {
1065
+ "package": self._scan_package_action_from_package,
1066
+ "product": self._scan_package_action_from_product,
1067
+ "packaging": self._scan_package_action_from_packaging,
1068
+ "lot": self._scan_package_action_from_lot,
1069
+ "serial": self._scan_package_action_from_serial,
1070
+ "delivery_packaging": self._scan_package_action_from_delivery_packaging,
1071
+ }
1072
+ search_result = self._scan_package_find(picking, barcode, handlers.keys())
1073
+ handler = handlers.get(search_result.type, self._scan_package_action_from_none)
1074
+ kwargs = {
1075
+ "barcode": barcode,
1076
+ "scanned_record": search_result.record,
1077
+ }
1078
+ return handler(picking, selected_lines, search_result.record, **kwargs)
1051
1079
 
1052
- def _scan_package_find(self, picking, barcode, search_types=None):
1080
+ def _scan_package_find(self, picking, barcode, search_types):
1053
1081
  search = self._actions_for("search")
1054
1082
  search_types = (
1055
1083
  "package",
@@ -1068,10 +1096,6 @@ class Checkout(Component):
1068
1096
  ),
1069
1097
  )
1070
1098
 
1071
- def _check_scan_package_find(self, picking, search_result):
1072
- # Used by inheriting modules
1073
- return False
1074
-
1075
1099
  def _find_line_to_increment(self, product_lines):
1076
1100
  """Find which line should have its qty incremented.
1077
1101
 
@@ -1186,7 +1210,7 @@ class Checkout(Component):
1186
1210
  * select_package: when no delivery packaging is available
1187
1211
  """
1188
1212
  picking = self.env["stock.picking"].browse(picking_id)
1189
- message = self._check_picking_status(picking)
1213
+ message = self._check_picking_processible(picking)
1190
1214
  if message:
1191
1215
  return self._response_for_select_document(message=message)
1192
1216
  selected_lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
@@ -1216,7 +1240,7 @@ class Checkout(Component):
1216
1240
  * select_line: goes back to selection of lines to work on next lines
1217
1241
  """
1218
1242
  picking = self.env["stock.picking"].browse(picking_id)
1219
- message = self._check_picking_status(picking)
1243
+ message = self._check_picking_processible(picking)
1220
1244
  if message:
1221
1245
  return self._response_for_select_document(message=message)
1222
1246
  packaging = None
@@ -1238,7 +1262,7 @@ class Checkout(Component):
1238
1262
  if self.options.get("checkout__disable_no_package"):
1239
1263
  raise BadRequest("`checkout.no_package` endpoint is not enabled")
1240
1264
  picking = self.env["stock.picking"].browse(picking_id)
1241
- message = self._check_picking_status(picking)
1265
+ message = self._check_picking_processible(picking)
1242
1266
  if message:
1243
1267
  return self._response_for_select_document(message=message)
1244
1268
  selected_lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
@@ -1270,7 +1294,7 @@ class Checkout(Component):
1270
1294
  * select_package: when no package is available
1271
1295
  """
1272
1296
  picking = self.env["stock.picking"].browse(picking_id)
1273
- message = self._check_picking_status(picking)
1297
+ message = self._check_picking_processible(picking)
1274
1298
  if message:
1275
1299
  return self._response_for_select_document(message=message)
1276
1300
  lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
@@ -1320,7 +1344,7 @@ class Checkout(Component):
1320
1344
  * summary: all lines are put in packages
1321
1345
  """
1322
1346
  picking = self.env["stock.picking"].browse(picking_id)
1323
- message = self._check_picking_status(picking)
1347
+ message = self._check_picking_processible(picking)
1324
1348
  if message:
1325
1349
  return self._response_for_select_document(message=message)
1326
1350
  lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
@@ -1347,7 +1371,7 @@ class Checkout(Component):
1347
1371
  * summary: all lines are put in packages
1348
1372
  """
1349
1373
  picking = self.env["stock.picking"].browse(picking_id)
1350
- message = self._check_picking_status(picking)
1374
+ message = self._check_picking_processible(picking)
1351
1375
  if message:
1352
1376
  return self._response_for_select_document(message=message)
1353
1377
  lines = self.env["stock.move.line"].browse(selected_line_ids).exists()
@@ -1374,7 +1398,7 @@ class Checkout(Component):
1374
1398
  * summary
1375
1399
  """
1376
1400
  picking = self.env["stock.picking"].browse(picking_id)
1377
- message = self._check_picking_status(picking)
1401
+ message = self._check_picking_processible(picking)
1378
1402
  if message:
1379
1403
  return self._response_for_select_document(message=message)
1380
1404
  return self._response_for_summary(picking)
@@ -1393,7 +1417,7 @@ class Checkout(Component):
1393
1417
  * summary: if the package_id no longer exists
1394
1418
  """
1395
1419
  picking = self.env["stock.picking"].browse(picking_id)
1396
- message = self._check_picking_status(picking)
1420
+ message = self._check_picking_processible(picking)
1397
1421
  if message:
1398
1422
  return self._response_for_select_document(message=message)
1399
1423
  package = self.env["stock.quant.package"].browse(package_id).exists()
@@ -1408,7 +1432,7 @@ class Checkout(Component):
1408
1432
  * summary
1409
1433
  """
1410
1434
  picking = self.env["stock.picking"].browse(picking_id)
1411
- message = self._check_picking_status(picking)
1435
+ message = self._check_picking_processible(picking)
1412
1436
  if message:
1413
1437
  return self._response_for_select_document(message=message)
1414
1438
 
@@ -1445,7 +1469,7 @@ class Checkout(Component):
1445
1469
  * select_line: when package or line has been canceled
1446
1470
  """
1447
1471
  picking = self.env["stock.picking"].browse(picking_id)
1448
- message = self._check_picking_status(picking)
1472
+ message = self._check_picking_processible(picking)
1449
1473
  if message:
1450
1474
  return self._response_for_select_document(message=message)
1451
1475
 
@@ -1490,7 +1514,7 @@ class Checkout(Component):
1490
1514
  * select_child_location: there are child destination locations
1491
1515
  """
1492
1516
  picking = self.env["stock.picking"].browse(picking_id)
1493
- message = self._check_picking_status(picking)
1517
+ message = self._check_picking_processible(picking)
1494
1518
  if message:
1495
1519
  return self._response_for_select_document(message=message)
1496
1520
  lines = picking.move_line_ids
@@ -1533,7 +1557,7 @@ class Checkout(Component):
1533
1557
  * select_child_location: in case of error
1534
1558
  """
1535
1559
  picking = self.env["stock.picking"].browse(picking_id)
1536
- message = self._check_picking_status(picking)
1560
+ message = self._check_picking_processible(picking)
1537
1561
  if message:
1538
1562
  return self._response_for_select_document(message=message)
1539
1563
  search = self._actions_for("search")