odoo-addon-shopfloor 16.0.1.0.0.25__py3-none-any.whl → 16.0.2.1.0__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 (76) hide show
  1. odoo/addons/shopfloor/README.rst +1 -1
  2. odoo/addons/shopfloor/__manifest__.py +1 -1
  3. odoo/addons/shopfloor/actions/data.py +69 -34
  4. odoo/addons/shopfloor/actions/data_detail.py +20 -0
  5. odoo/addons/shopfloor/actions/message.py +94 -2
  6. odoo/addons/shopfloor/actions/move_line_search.py +2 -2
  7. odoo/addons/shopfloor/actions/packaging.py +10 -0
  8. odoo/addons/shopfloor/actions/schema.py +11 -0
  9. odoo/addons/shopfloor/actions/schema_detail.py +14 -8
  10. odoo/addons/shopfloor/actions/search.py +9 -6
  11. odoo/addons/shopfloor/components/scan_handler_product.py +2 -0
  12. odoo/addons/shopfloor/data/shopfloor_scenario_data.xml +4 -2
  13. odoo/addons/shopfloor/docs/checkout_diag_seq.plantuml +19 -5
  14. odoo/addons/shopfloor/docs/checkout_diag_seq.png +0 -0
  15. odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.plantuml +4 -4
  16. odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.png +0 -0
  17. odoo/addons/shopfloor/i18n/ca.po +26 -8
  18. odoo/addons/shopfloor/i18n/de.po +26 -8
  19. odoo/addons/shopfloor/i18n/es_AR.po +36 -10
  20. odoo/addons/shopfloor/i18n/it.po +628 -491
  21. odoo/addons/shopfloor/i18n/pt_BR.po +26 -8
  22. odoo/addons/shopfloor/i18n/shopfloor.pot +133 -8
  23. odoo/addons/shopfloor/migrations/16.0.2.0.0/post-migration.py +43 -0
  24. odoo/addons/shopfloor/models/shopfloor_menu.py +29 -5
  25. odoo/addons/shopfloor/models/stock_move_line.py +3 -0
  26. odoo/addons/shopfloor/models/stock_picking.py +11 -0
  27. odoo/addons/shopfloor/services/checkout.py +216 -61
  28. odoo/addons/shopfloor/services/cluster_picking.py +33 -18
  29. odoo/addons/shopfloor/services/delivery.py +25 -7
  30. odoo/addons/shopfloor/services/location_content_transfer.py +42 -28
  31. odoo/addons/shopfloor/services/single_pack_transfer.py +36 -13
  32. odoo/addons/shopfloor/services/zone_picking.py +187 -67
  33. odoo/addons/shopfloor/static/description/index.html +1 -1
  34. odoo/addons/shopfloor/tests/__init__.py +2 -0
  35. odoo/addons/shopfloor/tests/common.py +3 -1
  36. odoo/addons/shopfloor/tests/test_actions_data.py +46 -7
  37. odoo/addons/shopfloor/tests/test_actions_data_base.py +15 -0
  38. odoo/addons/shopfloor/tests/test_actions_data_detail.py +30 -8
  39. odoo/addons/shopfloor/tests/test_actions_packaging.py +43 -0
  40. odoo/addons/shopfloor/tests/test_checkout_base.py +15 -5
  41. odoo/addons/shopfloor/tests/test_checkout_done.py +40 -5
  42. odoo/addons/shopfloor/tests/test_checkout_list_delivery_packaging.py +1 -0
  43. odoo/addons/shopfloor/tests/test_checkout_list_package.py +3 -1
  44. odoo/addons/shopfloor/tests/test_checkout_scan.py +19 -0
  45. odoo/addons/shopfloor/tests/test_checkout_scan_dest_location.py +99 -0
  46. odoo/addons/shopfloor/tests/test_checkout_scan_line.py +3 -2
  47. odoo/addons/shopfloor/tests/test_checkout_scan_line_no_prefill_qty.py +48 -0
  48. odoo/addons/shopfloor/tests/test_checkout_scan_package_action.py +26 -0
  49. odoo/addons/shopfloor/tests/test_checkout_scan_package_action_no_prefill_qty.py +16 -0
  50. odoo/addons/shopfloor/tests/test_checkout_select_package_base.py +4 -1
  51. odoo/addons/shopfloor/tests/test_checkout_summary.py +1 -1
  52. odoo/addons/shopfloor/tests/test_cluster_picking_unload.py +37 -8
  53. odoo/addons/shopfloor/tests/test_delivery_list_stock_picking.py +5 -0
  54. odoo/addons/shopfloor/tests/test_location_content_transfer_base.py +4 -4
  55. odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_all.py +24 -2
  56. odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_package_or_line.py +6 -4
  57. odoo/addons/shopfloor/tests/test_location_content_transfer_single.py +45 -0
  58. odoo/addons/shopfloor/tests/test_scan_anything.py +7 -0
  59. odoo/addons/shopfloor/tests/test_single_pack_transfer.py +59 -8
  60. odoo/addons/shopfloor/tests/test_zone_picking_base.py +36 -10
  61. odoo/addons/shopfloor/tests/test_zone_picking_change_pack_lot.py +2 -0
  62. odoo/addons/shopfloor/tests/test_zone_picking_complete_mix_pack_flux.py +59 -0
  63. odoo/addons/shopfloor/tests/test_zone_picking_select_line.py +33 -2
  64. odoo/addons/shopfloor/tests/test_zone_picking_select_line_first_scan_location.py +8 -3
  65. odoo/addons/shopfloor/tests/test_zone_picking_select_line_no_prefill_qty.py +19 -2
  66. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination.py +88 -19
  67. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_package_not_allowed.py +94 -0
  68. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_pick_pack.py +1 -5
  69. odoo/addons/shopfloor/tests/test_zone_picking_start.py +4 -4
  70. odoo/addons/shopfloor/tests/test_zone_picking_unload_all.py +1 -1
  71. odoo/addons/shopfloor/tests/test_zone_picking_unload_set_destination.py +4 -4
  72. odoo/addons/shopfloor/views/shopfloor_menu.xml +30 -0
  73. {odoo_addon_shopfloor-16.0.1.0.0.25.dist-info → odoo_addon_shopfloor-16.0.2.1.0.dist-info}/METADATA +2 -2
  74. {odoo_addon_shopfloor-16.0.1.0.0.25.dist-info → odoo_addon_shopfloor-16.0.2.1.0.dist-info}/RECORD +76 -71
  75. {odoo_addon_shopfloor-16.0.1.0.0.25.dist-info → odoo_addon_shopfloor-16.0.2.1.0.dist-info}/WHEEL +0 -0
  76. {odoo_addon_shopfloor-16.0.1.0.0.25.dist-info → odoo_addon_shopfloor-16.0.2.1.0.dist-info}/top_level.txt +0 -0
@@ -20,6 +20,16 @@ class CheckoutScanLineNoPrefillQtyCase(CheckoutScanLineCaseBase):
20
20
  cls._fill_stock_for_moves(move, in_lot=True)
21
21
  cls.picking.action_assign()
22
22
  cls.move_lines = cls.picking.move_line_ids
23
+ cls.delivery_packaging = (
24
+ cls.env["stock.package.type"]
25
+ .sudo()
26
+ .create(
27
+ {
28
+ "name": "DelivBox",
29
+ "barcode": "DelivBox",
30
+ }
31
+ )
32
+ )
23
33
 
24
34
  def _assert_quantity_done(self, barcode, selected_lines, qties):
25
35
  picking = selected_lines.mapped("picking_id")
@@ -89,3 +99,41 @@ class CheckoutScanLineNoPrefillQtyCase(CheckoutScanLineCaseBase):
89
99
  # should be the packaging qty, if a packaging is scanned
90
100
  qties = [1.0] * len(first_line)
91
101
  self._assert_quantity_done(lot.name, first_line, qties)
102
+
103
+ def test_scan_line_delivery_package_with_no_prefill_qty(self):
104
+ # Scan a delivery package
105
+ response = self.service.dispatch(
106
+ "scan_line",
107
+ params={
108
+ "picking_id": self.picking.id,
109
+ "barcode": self.delivery_packaging.barcode,
110
+ },
111
+ )
112
+ # Back to select_line asking to set some quantities
113
+ self.assertEqual(response["next_state"], "select_line")
114
+ self.assertEqual(
115
+ response["message"],
116
+ self.msg_store.no_lines_to_process_set_quantities(),
117
+ )
118
+ # Scan a product B to set some quantities
119
+ self.service.dispatch(
120
+ "scan_line",
121
+ params={
122
+ "picking_id": self.picking.id,
123
+ "barcode": self.product_b.barcode,
124
+ },
125
+ )
126
+ line_product_b = self.move_lines.filtered(
127
+ lambda line: line.product_id == self.product_b
128
+ )
129
+ self.assertFalse(line_product_b.result_package_id)
130
+ # Scan the delivery package again
131
+ response = self.service.dispatch(
132
+ "scan_line",
133
+ params={
134
+ "picking_id": self.picking.id,
135
+ "barcode": self.delivery_packaging.barcode,
136
+ },
137
+ )
138
+ # Check the line has been packed
139
+ self.assertTrue(line_product_b.result_package_id)
@@ -175,6 +175,7 @@ class CheckoutScanPackageActionCase(CheckoutCommonCase, CheckoutSelectPackageMix
175
175
  "no_package_enabled": not self.service.options.get(
176
176
  "checkout__disable_no_package"
177
177
  ),
178
+ "package_allowed": True,
178
179
  },
179
180
  message=self.service.msg_store.dest_package_not_valid(pack1),
180
181
  )
@@ -449,3 +450,28 @@ class CheckoutScanPackageActionCase(CheckoutCommonCase, CheckoutSelectPackageMix
449
450
  selected_line,
450
451
  message={"message_type": "error", "body": "Barcode not found"},
451
452
  )
453
+
454
+ def test_put_in_pack(self):
455
+ picking = self._create_picking(
456
+ lines=[(self.product_a, 10), (self.product_b, 20)]
457
+ )
458
+ self._fill_stock_for_moves(picking.move_ids)
459
+ picking.action_assign()
460
+
461
+ # Test that the move lines are marked as 'shopfloor_checkout_done'
462
+ # when putting them in a pack in the backend.
463
+ picking._put_in_pack(picking.move_line_ids)
464
+ self.assertTrue(
465
+ all(line.shopfloor_checkout_done for line in picking.move_line_ids)
466
+ )
467
+
468
+ # Check that we return those lines to the frontend.
469
+ res = self.service.dispatch(
470
+ "summary",
471
+ params={
472
+ "picking_id": picking.id,
473
+ },
474
+ )
475
+ returned_lines = res["data"]["summary"]["picking"]["move_lines"]
476
+ expected_line_ids = [line["id"] for line in returned_lines]
477
+ self.assertEqual(expected_line_ids, picking.move_line_ids.ids)
@@ -40,6 +40,8 @@ class CheckoutScanPackageActionCaseNoPrefillQty(
40
40
  """Scan a product which is present in two lines.
41
41
 
42
42
  Only one line should have its quantity incremented.
43
+ If one line has been fully processed,
44
+ then the second line will have its quantity incremented.
43
45
 
44
46
  """
45
47
  picking = self._create_picking(
@@ -64,6 +66,20 @@ class CheckoutScanPackageActionCaseNoPrefillQty(
64
66
  {move_lines[0]: 1, move_lines[1]: 0},
65
67
  )
66
68
 
69
+ # First line is fully processed,
70
+ # so we expect the second line to be incremented.
71
+ move_lines[0].qty_done = 3.0
72
+ self.service.dispatch(
73
+ "scan_package_action",
74
+ params={
75
+ "picking_id": picking.id,
76
+ "selected_line_ids": move_lines.ids,
77
+ "barcode": self.product_a.barcode,
78
+ },
79
+ )
80
+ self.assertEqual(move_lines[0].qty_done, 3.0)
81
+ self.assertEqual(move_lines[1].qty_done, 1.0)
82
+
67
83
  def test_scan_package_action_scan_lot_to_increment_qty(self):
68
84
  """ """
69
85
  picking = self._create_picking(lines=[(self.product_a, 3)])
@@ -10,6 +10,7 @@ class CheckoutSelectPackageMixin:
10
10
  message=None,
11
11
  packing_info="",
12
12
  no_package_enabled=True,
13
+ package_allowed=True,
13
14
  ):
14
15
  picking = selected_lines.mapped("picking_id")
15
16
  self.assert_response(
@@ -22,6 +23,7 @@ class CheckoutSelectPackageMixin:
22
23
  "picking": self._picking_summary_data(picking),
23
24
  "packing_info": packing_info,
24
25
  "no_package_enabled": no_package_enabled,
26
+ "package_allowed": package_allowed,
25
27
  },
26
28
  message=message,
27
29
  )
@@ -61,4 +63,5 @@ class CheckoutSelectPackageMixin:
61
63
  )
62
64
  for line in unselected_lines + related_lines:
63
65
  self.assertEqual(line.qty_done, 0)
64
- self._assert_selected_response(response, selected_lines, message=message, **kw)
66
+ package_lines = selected_lines + related_lines
67
+ self._assert_selected_response(response, package_lines, message=message, **kw)
@@ -26,7 +26,7 @@ class CheckoutSummaryCase(CheckoutCommonCase):
26
26
  self.assert_response(
27
27
  response,
28
28
  next_state="select_document",
29
- data={},
29
+ data={"restrict_scan_first": False},
30
30
  message=self.service.msg_store.stock_picking_not_available(self.picking),
31
31
  )
32
32
 
@@ -383,15 +383,17 @@ class ClusterPickingSetDestinationAllCase(ClusterPickingUnloadingCommonCase):
383
383
  self._set_dest_package_and_done(move_lines, self.bin1)
384
384
  move_lines.write({"location_dest_id": self.packing_a_location.id})
385
385
 
386
+ barcode = self.packing_b_location.barcode
386
387
  response = self.service.dispatch(
387
388
  "set_destination_all",
388
389
  params={
389
390
  "picking_batch_id": self.batch.id,
390
- "barcode": self.packing_b_location.barcode,
391
+ "barcode": barcode,
391
392
  },
392
393
  )
393
394
  location = move_lines[0].location_dest_id
394
395
  data = self._data_for_batch(self.batch, location)
396
+ data["confirmation"] = barcode
395
397
  self.assert_response(
396
398
  response,
397
399
  next_state="confirm_unload_all",
@@ -404,12 +406,13 @@ class ClusterPickingSetDestinationAllCase(ClusterPickingUnloadingCommonCase):
404
406
  self._set_dest_package_and_done(move_lines, self.bin1)
405
407
  move_lines.write({"location_dest_id": self.packing_a_location.id})
406
408
 
409
+ barcode = self.packing_b_location.barcode
407
410
  response = self.service.dispatch(
408
411
  "set_destination_all",
409
412
  params={
410
413
  "picking_batch_id": self.batch.id,
411
- "barcode": self.packing_b_location.barcode,
412
- "confirmation": True,
414
+ "barcode": barcode,
415
+ "confirmation": barcode,
413
416
  },
414
417
  )
415
418
  self.assertRecordValues(
@@ -426,6 +429,29 @@ class ClusterPickingSetDestinationAllCase(ClusterPickingUnloadingCommonCase):
426
429
  message={"message_type": "success", "body": "Batch Transfer complete"},
427
430
  )
428
431
 
432
+ def test_set_destination_all_check_confirmation(self):
433
+ """Endpoint called confirming with a different location, ask confirmation again"""
434
+ move_lines = self.move_lines
435
+ self._set_dest_package_and_done(move_lines, self.bin1)
436
+ move_lines.write({"location_dest_id": self.packing_a_location.id})
437
+
438
+ barcode = self.packing_b_location.barcode
439
+ response = self.service.dispatch(
440
+ "set_destination_all",
441
+ params={
442
+ "picking_batch_id": self.batch.id,
443
+ "barcode": barcode,
444
+ "confirmation": "other_barcode",
445
+ },
446
+ )
447
+ data = self._data_for_batch(self.batch, self.packing_a_location)
448
+ data["confirmation"] = barcode
449
+ self.assert_response(
450
+ response,
451
+ next_state="confirm_unload_all",
452
+ data=data,
453
+ )
454
+
429
455
 
430
456
  class ClusterPickingUnloadSplitCase(ClusterPickingUnloadingCommonCase):
431
457
  """Tests covering the /unload_split endpoint
@@ -610,7 +636,7 @@ class ClusterPickingUnloadScanDestinationCase(ClusterPickingUnloadingCommonCase)
610
636
  "picking_batch_id": self.batch.id,
611
637
  "package_id": self.bin1.id,
612
638
  "barcode": dest_location.barcode,
613
- "confirmation": True,
639
+ "confirmation": dest_location.barcode,
614
640
  },
615
641
  )
616
642
  self.assertRecordValues(
@@ -630,7 +656,7 @@ class ClusterPickingUnloadScanDestinationCase(ClusterPickingUnloadingCommonCase)
630
656
  "picking_batch_id": self.batch.id,
631
657
  "package_id": self.bin2.id,
632
658
  "barcode": dest_location.barcode,
633
- "confirmation": True,
659
+ "confirmation": dest_location.barcode,
634
660
  },
635
661
  )
636
662
  self.assertRecordValues(
@@ -825,16 +851,18 @@ class ClusterPickingUnloadScanDestinationCase(ClusterPickingUnloadingCommonCase)
825
851
 
826
852
  def test_unload_scan_destination_need_confirmation(self):
827
853
  """Endpoint called with a barcode for another (valid) location"""
854
+ barcode = self.packing_b_location.barcode
828
855
  response = self.service.dispatch(
829
856
  "unload_scan_destination",
830
857
  params={
831
858
  "picking_batch_id": self.batch.id,
832
859
  "package_id": self.bin1.id,
833
- "barcode": self.packing_b_location.barcode,
860
+ "barcode": barcode,
834
861
  },
835
862
  )
836
863
  location = self.bin1_lines[0].location_dest_id
837
864
  data = self._data_for_batch(self.batch, location, pack=self.bin1)
865
+ data["confirmation"] = barcode
838
866
  self.assert_response(
839
867
  response,
840
868
  next_state="confirm_unload_set_destination",
@@ -843,13 +871,14 @@ class ClusterPickingUnloadScanDestinationCase(ClusterPickingUnloadingCommonCase)
843
871
 
844
872
  def test_unload_scan_destination_with_confirmation(self):
845
873
  """Endpoint called with a barcode for another (valid) location, confirm"""
874
+ barcode = self.packing_a_location.barcode
846
875
  response = self.service.dispatch(
847
876
  "unload_scan_destination",
848
877
  params={
849
878
  "picking_batch_id": self.batch.id,
850
879
  "package_id": self.bin2.id,
851
- "barcode": self.packing_a_location.barcode,
852
- "confirmation": True,
880
+ "barcode": barcode,
881
+ "confirmation": barcode,
853
882
  },
854
883
  )
855
884
  self.assertRecordValues(
@@ -47,3 +47,8 @@ class DeliveryListStockPickingCase(DeliveryCommonCase):
47
47
  response,
48
48
  pickings=self.picking1 + self.picking2,
49
49
  )
50
+ # Cancel picking2
51
+ self.picking2.action_cancel()
52
+ response = self.service.dispatch("list_stock_picking", params={})
53
+ # Only picking1 is available
54
+ self.assert_response_manual_selection(response, pickings=self.picking1)
@@ -65,7 +65,7 @@ class LocationContentTransferCommonCase(CommonCase):
65
65
  )
66
66
 
67
67
  def _assert_response_scan_destination_all(
68
- self, state, response, pickings, message=None, confirmation_required=False
68
+ self, state, response, pickings, message=None, confirmation_required=None
69
69
  ):
70
70
  # this code is repeated from the implementation, not great, but we
71
71
  # mostly want to ensure the selection of pickings is right, and the
@@ -87,7 +87,7 @@ class LocationContentTransferCommonCase(CommonCase):
87
87
  )
88
88
 
89
89
  def assert_response_scan_destination_all(
90
- self, response, pickings, message=None, confirmation_required=False
90
+ self, response, pickings, message=None, confirmation_required=None
91
91
  ):
92
92
  self._assert_response_scan_destination_all(
93
93
  "scan_destination_all",
@@ -112,7 +112,7 @@ class LocationContentTransferCommonCase(CommonCase):
112
112
  )
113
113
 
114
114
  def _assert_response_scan_destination(
115
- self, state, response, next_content, message=None, confirmation_required=False
115
+ self, state, response, next_content, message=None, confirmation_required=None
116
116
  ):
117
117
  location = next_content.location_id
118
118
  data = self.service._data_content_line_for_location(location, next_content)
@@ -125,7 +125,7 @@ class LocationContentTransferCommonCase(CommonCase):
125
125
  )
126
126
 
127
127
  def assert_response_scan_destination(
128
- self, response, next_content, message=None, confirmation_required=False
128
+ self, response, next_content, message=None, confirmation_required=None
129
129
  ):
130
130
  self._assert_response_scan_destination(
131
131
  "scan_destination",
@@ -249,7 +249,7 @@ class LocationContentTransferSetDestinationAllCase(LocationContentTransferCommon
249
249
  response,
250
250
  self.pickings,
251
251
  message=self.service.msg_store.need_confirmation(),
252
- confirmation_required=True,
252
+ confirmation_required=self.shelf2.barcode,
253
253
  )
254
254
 
255
255
  def test_set_destination_all_dest_location_confirmation(self):
@@ -265,7 +265,7 @@ class LocationContentTransferSetDestinationAllCase(LocationContentTransferCommon
265
265
  # picking type's default dest location, ask confirmation (second scan)
266
266
  # from the user
267
267
  "barcode": self.shelf2.barcode,
268
- "confirmation": True,
268
+ "confirmation": self.shelf2.barcode,
269
269
  },
270
270
  )
271
271
  self.assert_response_start(
@@ -276,6 +276,28 @@ class LocationContentTransferSetDestinationAllCase(LocationContentTransferCommon
276
276
  )
277
277
  self.assert_all_done(self.shelf2)
278
278
 
279
+ def test_set_destination_all_dest_location_confirm_different(self):
280
+ """Scanned different location on location confirmation.
281
+
282
+ New confirmation is requested
283
+ """
284
+ response = self.service.dispatch(
285
+ "set_destination_all",
286
+ params={
287
+ "location_id": self.content_loc.id,
288
+ # expected shelf2 confirmation, but scanned shelf3
289
+ # another confirmation is required
290
+ "barcode": self.shelf3.barcode,
291
+ "confirmation": self.shelf2.barcode,
292
+ },
293
+ )
294
+ self.assert_response_scan_destination_all(
295
+ response,
296
+ self.pickings,
297
+ message=self.service.msg_store.need_confirmation(),
298
+ confirmation_required=self.shelf3.barcode,
299
+ )
300
+
279
301
  def test_set_destination_all_dest_location_invalid(self):
280
302
  """The scanned destination location is not in the menu's picking types"""
281
303
  response = self.service.dispatch(
@@ -146,6 +146,7 @@ class LocationContentTransferSetDestinationXCase(LocationContentTransferCommonCa
146
146
 
147
147
  def test_set_destination_package_dest_location_to_confirm(self):
148
148
  """Scanned destination location valid, but need a confirmation."""
149
+ barcode = self.env.ref("stock.stock_location_14").barcode
149
150
  package_level = self.picking1.package_level_ids[0]
150
151
  self._simulate_selected_move_line(package_level.move_line_ids)
151
152
  response = self.service.dispatch(
@@ -153,14 +154,14 @@ class LocationContentTransferSetDestinationXCase(LocationContentTransferCommonCa
153
154
  params={
154
155
  "location_id": self.content_loc.id,
155
156
  "package_level_id": package_level.id,
156
- "barcode": self.env.ref("stock.stock_location_14").barcode,
157
+ "barcode": barcode,
157
158
  },
158
159
  )
159
160
  self.assert_response_scan_destination(
160
161
  response,
161
162
  package_level,
162
163
  message=self.service.msg_store.need_confirmation(),
163
- confirmation_required=True,
164
+ confirmation_required=barcode,
164
165
  )
165
166
 
166
167
  def test_set_destination_package_dest_location_ok(self):
@@ -337,6 +338,7 @@ class LocationContentTransferSetDestinationXCase(LocationContentTransferCommonCa
337
338
 
338
339
  def test_set_destination_line_dest_location_to_confirm(self):
339
340
  """Scanned destination location valid, but need a confirmation."""
341
+ barcode = self.env.ref("stock.stock_location_14").barcode
340
342
  move_line = self.picking2.move_line_ids[0]
341
343
  self._simulate_selected_move_line(move_line)
342
344
  response = self.service.dispatch(
@@ -345,14 +347,14 @@ class LocationContentTransferSetDestinationXCase(LocationContentTransferCommonCa
345
347
  "location_id": self.content_loc.id,
346
348
  "move_line_id": move_line.id,
347
349
  "quantity": move_line.reserved_uom_qty,
348
- "barcode": self.env.ref("stock.stock_location_14").barcode,
350
+ "barcode": barcode,
349
351
  },
350
352
  )
351
353
  self.assert_response_scan_destination(
352
354
  response,
353
355
  move_line,
354
356
  message=self.service.msg_store.need_confirmation(),
355
- confirmation_required=True,
357
+ confirmation_required=barcode,
356
358
  )
357
359
 
358
360
  def test_set_destination_line_dest_location_ok(self):
@@ -552,6 +552,32 @@ class LocationContentTransferSingleCase(LocationContentTransferCommonCase):
552
552
  move_lines.mapped("picking_id"),
553
553
  )
554
554
 
555
+ def test_stock_out_package_ok_lines_not_owned_by_user(self):
556
+ """Declare a stock out on a package_level with lines not owned by user.
557
+
558
+ Same than the previous test, but lines will not be canceled, but
559
+ the assigned user will be removed.
560
+
561
+ """
562
+ self.env.user = self.shopfloor_manager
563
+ self.assertTrue(self.env.user != self.picking1.create_uid)
564
+ package_level = self.picking1.move_line_ids.package_level_id
565
+ move_lines_before = self.picking1.move_line_ids
566
+ move_lines_before.shopfloor_user_id = self.env.user
567
+ response = self.service.dispatch(
568
+ "stock_out_package",
569
+ params={
570
+ "location_id": self.content_loc.id,
571
+ "package_level_id": package_level.id,
572
+ },
573
+ )
574
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
575
+ self.assert_response_start_single(
576
+ response,
577
+ move_lines.mapped("picking_id"),
578
+ )
579
+ self.assertFalse(move_lines_before.exists())
580
+
555
581
  def test_stock_out_line_wrong_parameters(self):
556
582
  """Wrong 'location_id' and 'move_line_id' parameters, redirect the
557
583
  user to the 'start' screen.
@@ -746,3 +772,22 @@ class LocationContentTransferSingleSpecialCase(LocationContentTransferCommonCase
746
772
  response,
747
773
  move_lines.mapped("picking_id"),
748
774
  )
775
+
776
+ def test_stock_out_line_not_created_by_user(self):
777
+ """Declare a stock out on move line not owned by the user.
778
+
779
+ This will remove the assigned user but not cancel the move,
780
+ compare to the previous test.
781
+
782
+ """
783
+ self.env.user = self.shopfloor_manager
784
+ self.assertTrue(self.env.user != self.picking.create_uid)
785
+ move_line = self.move_product_b.move_line_ids.filtered(
786
+ lambda ml: ml.reserved_uom_qty == 4 # 4/10 to stock out
787
+ )
788
+ move_line.shopfloor_user_id = self.env.user
789
+ self.service.dispatch(
790
+ "stock_out_line",
791
+ params={"location_id": self.content_loc.id, "move_line_id": move_line.id},
792
+ )
793
+ self.assertFalse(move_line.exists())
@@ -47,3 +47,10 @@ class ScanAnythingCase(ActionsDataDetailCaseBase, ScanAnythingTestMixin):
47
47
  identifier = record.name
48
48
  data = self.data_detail.picking_detail(record)
49
49
  self._test_response_ok(rec_type, data, identifier)
50
+
51
+ def test_scan_packaging(self):
52
+ record = self.product_a_packaging
53
+ rec_type = "product"
54
+ identifier = record.barcode
55
+ data = self.data_detail.product_detail(record.product_id)
56
+ self._test_response_ok(rec_type, data, identifier)
@@ -79,6 +79,9 @@ class TestSinglePackTransfer(SinglePackTransferCommonBase):
79
79
  return {
80
80
  "id": package_level.id,
81
81
  "name": package_level.package_id.name,
82
+ "weight_uom": package_level.package_id.weight_uom_id.name,
83
+ "weight": package_level.package_id.pack_weight,
84
+ "estimated_weight_kg": package_level.package_id.estimated_pack_weight_kg,
82
85
  "location_src": self.data.location(package_level.location_id),
83
86
  "location_dest": self.data.location(package_level.location_dest_id),
84
87
  "picking": self.data.picking(self.picking),
@@ -121,7 +124,7 @@ class TestSinglePackTransfer(SinglePackTransferCommonBase):
121
124
  next_state="scan_location",
122
125
  data=dict(
123
126
  self._response_package_level_data(package_level),
124
- confirmation_required=False,
127
+ confirmation_required=None,
125
128
  ),
126
129
  )
127
130
 
@@ -189,13 +192,16 @@ class TestSinglePackTransfer(SinglePackTransferCommonBase):
189
192
  expected_data = {
190
193
  "id": package_level.id,
191
194
  "name": package_level.package_id.name,
195
+ "weight_uom": package_level.package_id.weight_uom_id.name,
196
+ "weight": package_level.package_id.pack_weight,
197
+ "estimated_weight_kg": package_level.package_id.estimated_pack_weight_kg,
192
198
  "location_src": self.data.location(self.shelf1),
193
199
  "location_dest": self.data.location(
194
200
  self.picking_type.default_location_dest_id
195
201
  ),
196
202
  "picking": self.data.picking(package_level.picking_id),
197
203
  "products": self.data.products(self.product_a),
198
- "confirmation_required": False,
204
+ "confirmation_required": None,
199
205
  }
200
206
 
201
207
  self.assert_response(response, next_state="scan_location", data=expected_data)
@@ -404,7 +410,7 @@ class TestSinglePackTransfer(SinglePackTransferCommonBase):
404
410
  },
405
411
  data=dict(
406
412
  self._response_package_level_data(package_level),
407
- confirmation_required=True,
413
+ confirmation_required=barcode,
408
414
  ),
409
415
  )
410
416
 
@@ -726,7 +732,7 @@ class TestSinglePackTransfer(SinglePackTransferCommonBase):
726
732
  message=message,
727
733
  data=dict(
728
734
  self._response_package_level_data(package_level),
729
- confirmation_required=True,
735
+ confirmation_required=sub_shelf2.barcode,
730
736
  ),
731
737
  )
732
738
 
@@ -761,7 +767,7 @@ class TestSinglePackTransfer(SinglePackTransferCommonBase):
761
767
  "package_level_id": package_level.id,
762
768
  "location_barcode": self.shelf2.barcode,
763
769
  # acknowledge the change of destination
764
- "confirmation": True,
770
+ "confirmation": self.shelf2.barcode,
765
771
  },
766
772
  )
767
773
 
@@ -783,9 +789,11 @@ class TestSinglePackTransfer(SinglePackTransferCommonBase):
783
789
  [{"location_dest_id": self.shelf2.id, "state": "done"}],
784
790
  )
785
791
 
786
- def test_cancel(self):
792
+ def test_cancel_transfer_not_created_by_user(self):
787
793
  """Test the happy path for single pack transfer /cancel endpoint
788
794
 
795
+ The transfer was not created by the shopfloor user.
796
+
789
797
  The pre-conditions:
790
798
 
791
799
  * /start has been called
@@ -797,6 +805,8 @@ class TestSinglePackTransfer(SinglePackTransferCommonBase):
797
805
  # setup the picking as we need, like if the move line
798
806
  # was already started by the first step (start operation)
799
807
  package_level = self._simulate_started(self.pack_a)
808
+ self.menu.sudo().allow_move_create = True
809
+ self.env.user = self.shopfloor_manager
800
810
  self.assertTrue(package_level.is_done)
801
811
 
802
812
  # keep references for later checks
@@ -809,7 +819,48 @@ class TestSinglePackTransfer(SinglePackTransferCommonBase):
809
819
  )
810
820
  self.assertRecordValues(move, [{"state": "assigned"}])
811
821
  self.assertRecordValues(picking, [{"state": "assigned"}])
812
- self.assertRecordValues(package_level, [{"is_done": False}])
822
+ self.assertTrue(move.move_line_ids.exists())
823
+ self.assertFalse(move.move_line_ids.shopfloor_user_id)
824
+ self.assert_response(
825
+ response,
826
+ next_state="start",
827
+ message={
828
+ "message_type": "success",
829
+ "body": "Canceled, you can scan a new pack.",
830
+ },
831
+ )
832
+
833
+ def test_cancel_transfer_created_by_user(self):
834
+ """Test the happy path for single pack transfer /cancel endpoint
835
+
836
+ The transfer was created by the shopfloor user.
837
+
838
+ The pre-conditions:
839
+
840
+ * /start has been called
841
+
842
+ Expected result:
843
+
844
+ * The package level has is_done to False
845
+ * The move and picking are canceled.
846
+ """
847
+ self.menu.sudo().allow_move_create = True
848
+ # setup the picking as we need, like if the move line
849
+ # was already started by the first step (start operation)
850
+ package_level = self._simulate_started(self.pack_a)
851
+ self.assertTrue(package_level.is_done)
852
+
853
+ # keep references for later checks
854
+ move = package_level.move_line_ids.move_id
855
+ picking = move.picking_id
856
+
857
+ # now, call the service to cancel
858
+ response = self.service.dispatch(
859
+ "cancel", params={"package_level_id": package_level.id}
860
+ )
861
+ self.assertRecordValues(move, [{"state": "cancel"}])
862
+ self.assertRecordValues(picking, [{"state": "cancel"}])
863
+ self.assertFalse(package_level.exists())
813
864
 
814
865
  self.assert_response(
815
866
  response,
@@ -994,7 +1045,7 @@ class SinglePackTransferSpecialCase(SinglePackTransferCommonBase):
994
1045
  next_state="scan_location",
995
1046
  data=dict(
996
1047
  self.service._data_after_package_scanned(new_package_level),
997
- confirmation_required=False,
1048
+ confirmation_required=None,
998
1049
  ),
999
1050
  )
1000
1051
  self.assertRecordValues(