odoo-addon-shopfloor 16.0.1.0.0.24__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 (182) hide show
  1. odoo/addons/shopfloor/README.rst +160 -0
  2. odoo/addons/shopfloor/__init__.py +4 -0
  3. odoo/addons/shopfloor/__manifest__.py +65 -0
  4. odoo/addons/shopfloor/actions/__init__.py +15 -0
  5. odoo/addons/shopfloor/actions/change_package_lot.py +164 -0
  6. odoo/addons/shopfloor/actions/completion_info.py +42 -0
  7. odoo/addons/shopfloor/actions/data.py +329 -0
  8. odoo/addons/shopfloor/actions/data_detail.py +154 -0
  9. odoo/addons/shopfloor/actions/inventory.py +150 -0
  10. odoo/addons/shopfloor/actions/location_content_transfer_sorter.py +89 -0
  11. odoo/addons/shopfloor/actions/message.py +846 -0
  12. odoo/addons/shopfloor/actions/move_line_search.py +119 -0
  13. odoo/addons/shopfloor/actions/packaging.py +59 -0
  14. odoo/addons/shopfloor/actions/savepoint.py +44 -0
  15. odoo/addons/shopfloor/actions/schema.py +182 -0
  16. odoo/addons/shopfloor/actions/schema_detail.py +98 -0
  17. odoo/addons/shopfloor/actions/search.py +187 -0
  18. odoo/addons/shopfloor/actions/stock.py +239 -0
  19. odoo/addons/shopfloor/actions/stock_unreserve.py +66 -0
  20. odoo/addons/shopfloor/components/__init__.py +5 -0
  21. odoo/addons/shopfloor/components/scan_handler_location.py +26 -0
  22. odoo/addons/shopfloor/components/scan_handler_lot.py +26 -0
  23. odoo/addons/shopfloor/components/scan_handler_package.py +26 -0
  24. odoo/addons/shopfloor/components/scan_handler_product.py +26 -0
  25. odoo/addons/shopfloor/components/scan_handler_transfer.py +26 -0
  26. odoo/addons/shopfloor/data/shopfloor_scenario_data.xml +73 -0
  27. odoo/addons/shopfloor/demo/shopfloor_app_demo.xml +12 -0
  28. odoo/addons/shopfloor/demo/shopfloor_menu_demo.xml +64 -0
  29. odoo/addons/shopfloor/demo/shopfloor_profile_demo.xml +8 -0
  30. odoo/addons/shopfloor/demo/stock_picking_type_demo.xml +93 -0
  31. odoo/addons/shopfloor/docs/checkout_diag_seq.plantuml +61 -0
  32. odoo/addons/shopfloor/docs/checkout_diag_seq.png +0 -0
  33. odoo/addons/shopfloor/docs/cluster_picking_diag_seq.plantuml +112 -0
  34. odoo/addons/shopfloor/docs/cluster_picking_diag_seq.png +0 -0
  35. odoo/addons/shopfloor/docs/delivery_diag_seq.plantuml +56 -0
  36. odoo/addons/shopfloor/docs/delivery_diag_seq.png +0 -0
  37. odoo/addons/shopfloor/docs/location_content_transfer_diag_seq.plantuml +66 -0
  38. odoo/addons/shopfloor/docs/location_content_transfer_diag_seq.png +0 -0
  39. odoo/addons/shopfloor/docs/oca_logo.png +0 -0
  40. odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.plantuml +36 -0
  41. odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.png +0 -0
  42. odoo/addons/shopfloor/docs/zone_picking_diag_seq.plantuml +85 -0
  43. odoo/addons/shopfloor/docs/zone_picking_diag_seq.png +0 -0
  44. odoo/addons/shopfloor/exceptions.py +6 -0
  45. odoo/addons/shopfloor/i18n/ca.po +1802 -0
  46. odoo/addons/shopfloor/i18n/de.po +1791 -0
  47. odoo/addons/shopfloor/i18n/es_AR.po +2147 -0
  48. odoo/addons/shopfloor/i18n/pt_BR.po +1791 -0
  49. odoo/addons/shopfloor/i18n/shopfloor.pot +1877 -0
  50. odoo/addons/shopfloor/models/__init__.py +12 -0
  51. odoo/addons/shopfloor/models/priority_postpone_mixin.py +41 -0
  52. odoo/addons/shopfloor/models/shopfloor_app.py +9 -0
  53. odoo/addons/shopfloor/models/shopfloor_menu.py +436 -0
  54. odoo/addons/shopfloor/models/stock_location.py +76 -0
  55. odoo/addons/shopfloor/models/stock_move.py +119 -0
  56. odoo/addons/shopfloor/models/stock_move_line.py +307 -0
  57. odoo/addons/shopfloor/models/stock_package_level.py +50 -0
  58. odoo/addons/shopfloor/models/stock_picking.py +118 -0
  59. odoo/addons/shopfloor/models/stock_picking_batch.py +41 -0
  60. odoo/addons/shopfloor/models/stock_picking_type.py +26 -0
  61. odoo/addons/shopfloor/models/stock_quant.py +31 -0
  62. odoo/addons/shopfloor/models/stock_quant_package.py +101 -0
  63. odoo/addons/shopfloor/readme/CONTRIBUTORS.rst +18 -0
  64. odoo/addons/shopfloor/readme/CREDITS.rst +5 -0
  65. odoo/addons/shopfloor/readme/DESCRIPTION.rst +17 -0
  66. odoo/addons/shopfloor/readme/HISTORY.rst +4 -0
  67. odoo/addons/shopfloor/readme/ROADMAP.rst +4 -0
  68. odoo/addons/shopfloor/readme/USAGE.rst +6 -0
  69. odoo/addons/shopfloor/security/groups.xml +17 -0
  70. odoo/addons/shopfloor/services/__init__.py +16 -0
  71. odoo/addons/shopfloor/services/checkout.py +1763 -0
  72. odoo/addons/shopfloor/services/cluster_picking.py +1628 -0
  73. odoo/addons/shopfloor/services/delivery.py +828 -0
  74. odoo/addons/shopfloor/services/forms/__init__.py +1 -0
  75. odoo/addons/shopfloor/services/forms/picking_form.py +78 -0
  76. odoo/addons/shopfloor/services/location_content_transfer.py +1194 -0
  77. odoo/addons/shopfloor/services/menu.py +60 -0
  78. odoo/addons/shopfloor/services/picking_batch.py +126 -0
  79. odoo/addons/shopfloor/services/service.py +101 -0
  80. odoo/addons/shopfloor/services/single_pack_transfer.py +366 -0
  81. odoo/addons/shopfloor/services/zone_picking.py +1938 -0
  82. odoo/addons/shopfloor/static/description/icon.png +0 -0
  83. odoo/addons/shopfloor/static/description/index.html +500 -0
  84. odoo/addons/shopfloor/tests/__init__.py +83 -0
  85. odoo/addons/shopfloor/tests/common.py +324 -0
  86. odoo/addons/shopfloor/tests/models.py +29 -0
  87. odoo/addons/shopfloor/tests/test_actions_change_package_lot.py +1175 -0
  88. odoo/addons/shopfloor/tests/test_actions_data.py +376 -0
  89. odoo/addons/shopfloor/tests/test_actions_data_base.py +244 -0
  90. odoo/addons/shopfloor/tests/test_actions_data_detail.py +322 -0
  91. odoo/addons/shopfloor/tests/test_actions_search.py +248 -0
  92. odoo/addons/shopfloor/tests/test_actions_stock.py +48 -0
  93. odoo/addons/shopfloor/tests/test_checkout_auto_post.py +67 -0
  94. odoo/addons/shopfloor/tests/test_checkout_base.py +81 -0
  95. odoo/addons/shopfloor/tests/test_checkout_cancel_line.py +154 -0
  96. odoo/addons/shopfloor/tests/test_checkout_change_packaging.py +184 -0
  97. odoo/addons/shopfloor/tests/test_checkout_done.py +133 -0
  98. odoo/addons/shopfloor/tests/test_checkout_list_delivery_packaging.py +131 -0
  99. odoo/addons/shopfloor/tests/test_checkout_list_package.py +327 -0
  100. odoo/addons/shopfloor/tests/test_checkout_new_package.py +88 -0
  101. odoo/addons/shopfloor/tests/test_checkout_no_package.py +95 -0
  102. odoo/addons/shopfloor/tests/test_checkout_scan.py +174 -0
  103. odoo/addons/shopfloor/tests/test_checkout_scan_line.py +377 -0
  104. odoo/addons/shopfloor/tests/test_checkout_scan_line_base.py +25 -0
  105. odoo/addons/shopfloor/tests/test_checkout_scan_line_no_prefill_qty.py +91 -0
  106. odoo/addons/shopfloor/tests/test_checkout_scan_package_action.py +451 -0
  107. odoo/addons/shopfloor/tests/test_checkout_scan_package_action_no_prefill_qty.py +107 -0
  108. odoo/addons/shopfloor/tests/test_checkout_select.py +74 -0
  109. odoo/addons/shopfloor/tests/test_checkout_select_line.py +130 -0
  110. odoo/addons/shopfloor/tests/test_checkout_select_package_base.py +64 -0
  111. odoo/addons/shopfloor/tests/test_checkout_set_qty.py +257 -0
  112. odoo/addons/shopfloor/tests/test_checkout_summary.py +69 -0
  113. odoo/addons/shopfloor/tests/test_cluster_picking_base.py +83 -0
  114. odoo/addons/shopfloor/tests/test_cluster_picking_batch.py +109 -0
  115. odoo/addons/shopfloor/tests/test_cluster_picking_change_pack_lot.py +111 -0
  116. odoo/addons/shopfloor/tests/test_cluster_picking_is_zero.py +98 -0
  117. odoo/addons/shopfloor/tests/test_cluster_picking_scan_destination.py +376 -0
  118. odoo/addons/shopfloor/tests/test_cluster_picking_scan_destination_no_prefill_qty.py +115 -0
  119. odoo/addons/shopfloor/tests/test_cluster_picking_scan_line.py +402 -0
  120. odoo/addons/shopfloor/tests/test_cluster_picking_scan_line_location_or_pack_first.py +114 -0
  121. odoo/addons/shopfloor/tests/test_cluster_picking_scan_line_no_prefill_qty.py +70 -0
  122. odoo/addons/shopfloor/tests/test_cluster_picking_select.py +387 -0
  123. odoo/addons/shopfloor/tests/test_cluster_picking_skip.py +90 -0
  124. odoo/addons/shopfloor/tests/test_cluster_picking_stock_issue.py +364 -0
  125. odoo/addons/shopfloor/tests/test_cluster_picking_unload.py +911 -0
  126. odoo/addons/shopfloor/tests/test_delivery_base.py +155 -0
  127. odoo/addons/shopfloor/tests/test_delivery_done.py +108 -0
  128. odoo/addons/shopfloor/tests/test_delivery_list_stock_picking.py +49 -0
  129. odoo/addons/shopfloor/tests/test_delivery_reset_qty_done_line.py +119 -0
  130. odoo/addons/shopfloor/tests/test_delivery_reset_qty_done_pack.py +107 -0
  131. odoo/addons/shopfloor/tests/test_delivery_scan_deliver.py +557 -0
  132. odoo/addons/shopfloor/tests/test_delivery_select.py +38 -0
  133. odoo/addons/shopfloor/tests/test_delivery_set_qty_done_line.py +91 -0
  134. odoo/addons/shopfloor/tests/test_delivery_set_qty_done_pack.py +135 -0
  135. odoo/addons/shopfloor/tests/test_delivery_sublocation.py +180 -0
  136. odoo/addons/shopfloor/tests/test_location_content_transfer_base.py +136 -0
  137. odoo/addons/shopfloor/tests/test_location_content_transfer_get_work.py +125 -0
  138. odoo/addons/shopfloor/tests/test_location_content_transfer_mix.py +509 -0
  139. odoo/addons/shopfloor/tests/test_location_content_transfer_putaway.py +143 -0
  140. odoo/addons/shopfloor/tests/test_location_content_transfer_scan_location.py +34 -0
  141. odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_all.py +343 -0
  142. odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_package_or_line.py +1074 -0
  143. odoo/addons/shopfloor/tests/test_location_content_transfer_single.py +748 -0
  144. odoo/addons/shopfloor/tests/test_location_content_transfer_start.py +359 -0
  145. odoo/addons/shopfloor/tests/test_menu_base.py +261 -0
  146. odoo/addons/shopfloor/tests/test_menu_counters.py +61 -0
  147. odoo/addons/shopfloor/tests/test_misc.py +25 -0
  148. odoo/addons/shopfloor/tests/test_move_action_assign.py +87 -0
  149. odoo/addons/shopfloor/tests/test_openapi.py +21 -0
  150. odoo/addons/shopfloor/tests/test_picking_form.py +62 -0
  151. odoo/addons/shopfloor/tests/test_scan_anything.py +49 -0
  152. odoo/addons/shopfloor/tests/test_single_pack_transfer.py +1121 -0
  153. odoo/addons/shopfloor/tests/test_single_pack_transfer_base.py +32 -0
  154. odoo/addons/shopfloor/tests/test_single_pack_transfer_putaway.py +104 -0
  155. odoo/addons/shopfloor/tests/test_stock_split.py +204 -0
  156. odoo/addons/shopfloor/tests/test_user.py +42 -0
  157. odoo/addons/shopfloor/tests/test_zone_picking_base.py +608 -0
  158. odoo/addons/shopfloor/tests/test_zone_picking_change_pack_lot.py +140 -0
  159. odoo/addons/shopfloor/tests/test_zone_picking_select_line.py +723 -0
  160. odoo/addons/shopfloor/tests/test_zone_picking_select_line_first_scan_location.py +207 -0
  161. odoo/addons/shopfloor/tests/test_zone_picking_select_line_first_scan_location.py.bak +202 -0
  162. odoo/addons/shopfloor/tests/test_zone_picking_select_line_no_prefill_qty.py +107 -0
  163. odoo/addons/shopfloor/tests/test_zone_picking_select_picking_type.py +26 -0
  164. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination.py +643 -0
  165. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_no_prefill_qty.py +146 -0
  166. odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_pick_pack.py +241 -0
  167. odoo/addons/shopfloor/tests/test_zone_picking_start.py +206 -0
  168. odoo/addons/shopfloor/tests/test_zone_picking_stock_issue.py +121 -0
  169. odoo/addons/shopfloor/tests/test_zone_picking_unload_all.py +353 -0
  170. odoo/addons/shopfloor/tests/test_zone_picking_unload_buffer_lines.py +113 -0
  171. odoo/addons/shopfloor/tests/test_zone_picking_unload_set_destination.py +374 -0
  172. odoo/addons/shopfloor/tests/test_zone_picking_unload_single.py +123 -0
  173. odoo/addons/shopfloor/tests/test_zone_picking_zero_check.py +43 -0
  174. odoo/addons/shopfloor/utils.py +13 -0
  175. odoo/addons/shopfloor/views/shopfloor_menu.xml +167 -0
  176. odoo/addons/shopfloor/views/stock_location.xml +20 -0
  177. odoo/addons/shopfloor/views/stock_move_line.xml +52 -0
  178. odoo/addons/shopfloor/views/stock_picking_type.xml +19 -0
  179. odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/METADATA +192 -0
  180. odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/RECORD +182 -0
  181. odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/WHEEL +5 -0
  182. odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/top_level.txt +1 -0
@@ -0,0 +1,115 @@
1
+ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+
4
+ from .test_cluster_picking_base import ClusterPickingCommonCase
5
+
6
+ # pylint: disable=missing-return
7
+
8
+
9
+ class ClusterPickingScanDestinationPackPrefillQtyCase(ClusterPickingCommonCase):
10
+ """Tests covering the /scan_destination_pack endpoint
11
+
12
+ With the no prefill quantity option set
13
+
14
+ """
15
+
16
+ @classmethod
17
+ def setUpClassBaseData(cls, *args, **kwargs):
18
+ super().setUpClassBaseData(*args, **kwargs)
19
+ cls.menu.sudo().no_prefill_qty = True
20
+ cls.batch = cls._create_picking_batch(
21
+ [
22
+ [
23
+ cls.BatchProduct(product=cls.product_a, quantity=10),
24
+ cls.BatchProduct(product=cls.product_b, quantity=10),
25
+ ],
26
+ [cls.BatchProduct(product=cls.product_a, quantity=10)],
27
+ ]
28
+ )
29
+ cls.one_line_picking = cls.batch.picking_ids.filtered(
30
+ lambda picking: len(picking.move_ids) == 1
31
+ )
32
+ cls.two_lines_picking = cls.batch.picking_ids.filtered(
33
+ lambda picking: len(picking.move_ids) == 2
34
+ )
35
+
36
+ cls.bin1 = cls.env["stock.quant.package"].create({})
37
+ cls.bin2 = cls.env["stock.quant.package"].create({})
38
+
39
+ cls._simulate_batch_selected(cls.batch)
40
+
41
+ def test_scan_destination_pack_increment_with_product(self):
42
+ """Check quantity increment by scanning the product."""
43
+ line = self.batch.picking_ids.move_line_ids[0]
44
+ previous_qty_done = line.qty_done
45
+ for qty_done in range(1, 2):
46
+ response = self.service.dispatch(
47
+ "scan_destination_pack",
48
+ params={
49
+ "picking_batch_id": self.batch.id,
50
+ "move_line_id": line.id,
51
+ "barcode": line.product_id.barcode,
52
+ "quantity": qty_done,
53
+ },
54
+ )
55
+ # Ensure the qty has not changed.
56
+ self.assertEqual(line.qty_done, previous_qty_done)
57
+
58
+ expected_qty_done = qty_done + 1
59
+ self.assert_response(
60
+ response,
61
+ next_state="scan_destination",
62
+ data=self._line_data(line, qty_done=expected_qty_done),
63
+ )
64
+
65
+ def test_scan_destination_pack_increment_with_wrong_product(self):
66
+ """Check quantity is not incremented by scanning the wrong product."""
67
+ line = self.batch.picking_ids.move_line_ids[0]
68
+ previous_qty_done = line.qty_done
69
+ qty_done = 2
70
+ response = self.service.dispatch(
71
+ "scan_destination_pack",
72
+ params={
73
+ "picking_batch_id": self.batch.id,
74
+ "move_line_id": line.id,
75
+ "barcode": self.product_b.barcode,
76
+ "quantity": qty_done,
77
+ },
78
+ )
79
+ # Ensure the qty has not changed.
80
+ self.assertEqual(line.qty_done, previous_qty_done)
81
+
82
+ expected_qty_done = qty_done
83
+ self.assert_response(
84
+ response,
85
+ next_state="scan_destination",
86
+ data=self._line_data(line, qty_done=expected_qty_done),
87
+ message=self.service.msg_store.bin_not_found_for_barcode(
88
+ self.product_b.barcode
89
+ ),
90
+ )
91
+
92
+ def test_scan_destination_pack_increment_with_packaging(self):
93
+ """Check quantity incremented by scanning the packaging."""
94
+ line = self.batch.picking_ids.move_line_ids[0]
95
+ previous_qty_done = line.qty_done
96
+ packaging = self.product_a_packaging
97
+ qty_done = 2
98
+ response = self.service.dispatch(
99
+ "scan_destination_pack",
100
+ params={
101
+ "picking_batch_id": self.batch.id,
102
+ "move_line_id": line.id,
103
+ "barcode": packaging.barcode,
104
+ "quantity": qty_done,
105
+ },
106
+ )
107
+ # Ensure the qty has not changed in the record.
108
+ self.assertEqual(line.qty_done, previous_qty_done)
109
+
110
+ expected_qty_done = qty_done + packaging.qty
111
+ self.assert_response(
112
+ response,
113
+ next_state="scan_destination",
114
+ data=self._line_data(line, qty_done=expected_qty_done),
115
+ )
@@ -0,0 +1,402 @@
1
+ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+
4
+ from .test_cluster_picking_base import ClusterPickingLineCommonCase
5
+
6
+
7
+ class ClusterPickingScanLineCase(ClusterPickingLineCommonCase):
8
+ """Tests covering the /scan_line endpoint
9
+
10
+ After a batch has been selected and the user confirmed they are
11
+ working on it.
12
+
13
+ User scans something and the scan_line endpoints validates they
14
+ scanned the proper thing to pick.
15
+ """
16
+
17
+ def _scan_line_ok(self, line, scanned, expected_qty_done=1):
18
+ batch = line.picking_id.batch_id
19
+ previous_qty_done = line.qty_done
20
+ response = self.service.dispatch(
21
+ "scan_line",
22
+ params={
23
+ "picking_batch_id": batch.id,
24
+ "move_line_id": line.id,
25
+ "barcode": scanned,
26
+ },
27
+ )
28
+ # For any barcode scanned, the quantity done is set in
29
+ # the response data to fully done but the record is not updated.
30
+ # We ensure the qty has not changed in the record.
31
+ self.assertEqual(line.qty_done, previous_qty_done)
32
+
33
+ self.assert_response(
34
+ response,
35
+ next_state="scan_destination",
36
+ data=self._line_data(line, qty_done=expected_qty_done),
37
+ )
38
+
39
+ def _scan_line_error(self, line, scanned, message, sublocation=None):
40
+ batch = line.picking_id.batch_id
41
+ response = self.service.dispatch(
42
+ "scan_line",
43
+ params={
44
+ "picking_batch_id": batch.id,
45
+ "move_line_id": line.id,
46
+ "barcode": scanned,
47
+ },
48
+ )
49
+ kw = {"sublocation": self.data.location(sublocation)} if sublocation else {}
50
+ self.assert_response(
51
+ response,
52
+ next_state="start_line",
53
+ data=self._line_data(line, **kw),
54
+ message=message,
55
+ )
56
+
57
+ def test_scan_line_pack_ok(self):
58
+ """Scan to check if user picks the correct pack for current line"""
59
+ self._simulate_batch_selected(self.batch, in_package=True)
60
+ line = self.batch.picking_ids.move_line_ids
61
+ self._scan_line_ok(line, line.package_id.name)
62
+
63
+ def test_scan_line_product_ok(self):
64
+ """Scan to check if user picks the correct product for current line"""
65
+ self._simulate_batch_selected(self.batch)
66
+ line = self.batch.picking_ids.move_line_ids
67
+ self._scan_line_ok(line, line.product_id.barcode)
68
+
69
+ def test_scan_line_lot_ok(self):
70
+ """Scan to check if user picks the correct lot for current line"""
71
+ self.product_a.tracking = "lot"
72
+ self._simulate_batch_selected(self.batch, in_lot=True)
73
+ line = self.batch.picking_ids.move_line_ids
74
+ self._scan_line_ok(line, line.lot_id.name)
75
+
76
+ def test_scan_line_serial_ok(self):
77
+ """Scan to check if user picks the correct serial for current line"""
78
+ self.product_a.tracking = "serial"
79
+ self._simulate_batch_selected(self.batch, in_lot=True)
80
+ line = self.batch.picking_ids.move_line_ids
81
+ self._scan_line_ok(line, line.lot_id.name)
82
+
83
+ def test_scan_line_error_product_tracked(self):
84
+ """Scan a product tracked by lot, must scan the lot.
85
+
86
+ If for the same product there is multiple lots in the location,
87
+ the user must scan the lot.
88
+ """
89
+ self.product_a.tracking = "lot"
90
+ self._simulate_batch_selected(self.batch, in_lot=True)
91
+ line = self.batch.picking_ids.move_line_ids
92
+ # Add another lot for the same product in the location
93
+ location = self.batch.picking_ids.location_id
94
+ new_lot = self.env["stock.lot"].create(
95
+ {"product_id": self.product_a.id, "company_id": self.env.company.id}
96
+ )
97
+ self._update_qty_in_location(location, line.product_id, 2, lot=new_lot)
98
+ self._scan_line_error(
99
+ line,
100
+ line.product_id.barcode,
101
+ {
102
+ "message_type": "warning",
103
+ "body": "Product tracked by lot, please scan one.",
104
+ },
105
+ )
106
+
107
+ def test_scan_line_lot_ok_only_one_in_location(self):
108
+ """Scan a product tracked by lot but no error.
109
+
110
+ If only one lot for that product is in the location, it can
111
+ be safely picked up.
112
+ """
113
+ self.product_a.tracking = "lot"
114
+ self._simulate_batch_selected(self.batch, in_lot=True)
115
+ line = self.batch.picking_ids.move_line_ids
116
+ self._scan_line_ok(line, line.lot_id.name)
117
+
118
+ def test_scan_line_product_error_several_packages(self):
119
+ """When we scan a product which is in more than one package, error"""
120
+ self._simulate_batch_selected(self.batch, in_package=True)
121
+ line = self.batch.picking_ids.move_line_ids
122
+ # create a second move line for the same product in a different
123
+ # package
124
+ move = line.move_id.copy()
125
+ self._fill_stock_for_moves(move, in_package=True)
126
+ move._action_confirm(merge=False)
127
+ move._action_assign()
128
+
129
+ self._scan_line_error(
130
+ line,
131
+ move.product_id.barcode,
132
+ {
133
+ "message_type": "warning",
134
+ "body": "This product is part of multiple"
135
+ " packages, please scan a package.",
136
+ },
137
+ )
138
+
139
+ def test_scan_line_product_error_in_one_package_and_raw_same_location(self):
140
+ """Scan product which is both in a package and as raw in same location"""
141
+ self._simulate_batch_selected(self.batch, in_package=True)
142
+ line = self.batch.picking_ids.move_line_ids
143
+ # create a second move line for the same product in a different
144
+ # package
145
+ move = line.move_id.copy()
146
+ self._fill_stock_for_moves(move)
147
+ move._action_confirm(merge=False)
148
+ move._action_assign()
149
+ move.move_line_ids[0].package_id = None
150
+
151
+ self._scan_line_error(
152
+ line,
153
+ move.product_id.barcode,
154
+ {
155
+ "message_type": "warning",
156
+ "body": "This product is part of multiple"
157
+ " packages, please scan a package.",
158
+ },
159
+ )
160
+
161
+ def test_scan_line_product_error_in_one_package_and_raw_different_location(self):
162
+ """Scan product which is both in a package and as raw in another location"""
163
+ self._simulate_batch_selected(self.batch, in_package=True)
164
+ line = self.batch.picking_ids.move_line_ids
165
+ # create a second move line for the same product in a different
166
+ # package
167
+ move = line.move_id.copy()
168
+ move.location_id = line.location_id.copy()
169
+ self._fill_stock_for_moves(move)
170
+ move._action_confirm(merge=False)
171
+ move._action_assign()
172
+ move.move_line_ids[0].package_id = None
173
+ move.move_line_ids[0].location_id = line.location_id.copy()
174
+ self._scan_line_ok(line, move.product_id.barcode)
175
+
176
+ def test_scan_line_lot_error_several_packages(self):
177
+ """When we scan a lot which is in more than one package, error"""
178
+ self._simulate_batch_selected(self.batch, in_package=True, in_lot=True)
179
+ line = self.batch.picking_ids.move_line_ids
180
+ # create a second move line for the same product in a different
181
+ # package
182
+ move = line.move_id.copy()
183
+ self._fill_stock_for_moves(move, in_lot=line.lot_id)
184
+ move._action_confirm(merge=False)
185
+ move._action_assign()
186
+
187
+ self._scan_line_error(
188
+ line,
189
+ line.lot_id.name,
190
+ {
191
+ "message_type": "warning",
192
+ "body": "This lot is part of multiple"
193
+ " packages, please scan a package.",
194
+ },
195
+ )
196
+
197
+ def test_scan_line_lot_error_in_one_package_and_unit(self):
198
+ """When we scan a lot which is in a package and as raw, error"""
199
+ self._simulate_batch_selected(self.batch, in_package=True, in_lot=True)
200
+ line = self.batch.picking_ids.move_line_ids
201
+ # create a second move line for the same product in a different
202
+ # package
203
+ move = line.move_id.copy()
204
+ self._fill_stock_for_moves(move, in_lot=line.lot_id)
205
+ move._action_confirm(merge=False)
206
+ move._action_assign()
207
+ self._scan_line_error(
208
+ line,
209
+ line.lot_id.name,
210
+ {
211
+ "message_type": "warning",
212
+ "body": "This lot is part of multiple"
213
+ " packages, please scan a package.",
214
+ },
215
+ )
216
+
217
+ def test_scan_line_location_ok_single_package(self):
218
+ """Scan to check if user scans a correct location for current line
219
+
220
+ If there is only one single package in the location, there is no
221
+ ambiguity so we can use it.
222
+ """
223
+ self._simulate_batch_selected(self.batch, in_package=True)
224
+ line = self.batch.picking_ids.move_line_ids
225
+ self._scan_line_ok(line, line.location_id.barcode)
226
+
227
+ def test_scan_line_location_ok_single_product(self):
228
+ """Scan to check if user scans a correct location for current line
229
+
230
+ If there is only one single product in the location, there is no
231
+ ambiguity so we can use it.
232
+ """
233
+ self._simulate_batch_selected(self.batch)
234
+ line = self.batch.picking_ids.move_line_ids
235
+ self._scan_line_ok(line, line.location_id.barcode)
236
+
237
+ def test_scan_line_location_ok_single_lot(self):
238
+ """Scan to check if user scans a correct location for current line
239
+
240
+ If there is only one single lot in the location, there is no
241
+ ambiguity so we can use it.
242
+ """
243
+ self._simulate_batch_selected(self.batch, in_lot=True)
244
+ line = self.batch.picking_ids.move_line_ids
245
+ self._scan_line_ok(line, line.location_id.barcode)
246
+
247
+ def test_scan_line_location_error_several_package(self):
248
+ """Scan to check if user scans a correct location for current line
249
+
250
+ If there are several packages in the location, user has to scan one.
251
+ """
252
+ self._simulate_batch_selected(self.batch, in_package=True)
253
+ line = self.batch.picking_ids.move_line_ids
254
+ location = line.location_id
255
+ pack_1 = line.package_id
256
+ # add a second package for another product in the location
257
+ self._create_package_in_location(
258
+ location, [self.PackageContent(self.product_b, 10, lot=None)]
259
+ )
260
+ # it leads to an error, but now the location is kept as working location
261
+ self._scan_line_error(
262
+ line,
263
+ location.barcode,
264
+ {
265
+ "message_type": "warning",
266
+ "body": "Several packages found in Stock, please scan a package.",
267
+ },
268
+ sublocation=location,
269
+ )
270
+ # scanning the package works
271
+ self._scan_line_ok(line, pack_1.name)
272
+
273
+ def test_scan_line_location_error_several_products(self):
274
+ """Scan to check if user scans a correct location for current line
275
+
276
+ If there are several products in the location, user has to scan one.
277
+ """
278
+ self._simulate_batch_selected(self.batch)
279
+ line = self.batch.picking_ids.move_line_ids
280
+ location = line.location_id
281
+ # add a second product in the location, leads to an error
282
+ self._update_qty_in_location(location, self.product_b, 10)
283
+ self._scan_line_error(
284
+ line,
285
+ location.barcode,
286
+ {
287
+ "message_type": "warning",
288
+ "body": "Several products found in Stock, please scan a product.",
289
+ },
290
+ sublocation=location,
291
+ )
292
+ self._scan_line_ok(line, self.product_a.barcode)
293
+
294
+ def test_scan_line_location_error_several_lots(self):
295
+ """Scan to check if user scans a correct location for current line
296
+
297
+ If there are several lots in the location, user has to scan one.
298
+ """
299
+ self._simulate_batch_selected(self.batch, in_lot=True)
300
+ line = self.batch.picking_ids.move_line_ids
301
+ location = line.location_id
302
+ # add a 2nd lot in the same location
303
+ lot_2 = (
304
+ self.env["stock.lot"]
305
+ .sudo()
306
+ .create(
307
+ {
308
+ "name": "LOT_2nd",
309
+ "product_id": line.product_id.id,
310
+ "company_id": self.env.company.id,
311
+ }
312
+ )
313
+ )
314
+ self._update_qty_in_location(location, line.product_id, 10, lot=lot_2)
315
+ # leads to an error
316
+ self._scan_line_error(
317
+ line,
318
+ location.barcode,
319
+ {
320
+ "message_type": "warning",
321
+ "body": "Several lots found in Stock, please scan a lot.",
322
+ },
323
+ sublocation=location,
324
+ )
325
+ self._scan_line_ok(line, line.lot_id.name)
326
+
327
+ def test_scan_line_error_wrong_package(self):
328
+ """Wrong package scanned"""
329
+ self._simulate_batch_selected(self.batch, in_package=True)
330
+ pack = self.env["stock.quant.package"].sudo().create({})
331
+ self._scan_line_error(
332
+ self.batch.picking_ids.move_line_ids,
333
+ pack.name,
334
+ {"message_type": "error", "body": "Wrong pack."},
335
+ )
336
+
337
+ def test_scan_line_error_wrong_product(self):
338
+ """Wrong product scanned"""
339
+ self._simulate_batch_selected(self.batch, in_package=True)
340
+ product = (
341
+ self.env["product.product"]
342
+ .sudo()
343
+ .create(
344
+ {
345
+ "name": "Wrong",
346
+ "barcode": "WRONGPRODUCT",
347
+ }
348
+ )
349
+ )
350
+ self._scan_line_error(
351
+ self.batch.picking_ids.move_line_ids,
352
+ product.barcode,
353
+ {"message_type": "error", "body": "Wrong product."},
354
+ )
355
+
356
+ def test_scan_line_error_wrong_lot(self):
357
+ """Wrong product scanned"""
358
+ self._simulate_batch_selected(self.batch, in_package=True)
359
+ lot = (
360
+ self.env["stock.lot"]
361
+ .sudo()
362
+ .create(
363
+ {
364
+ "name": "WRONGLOT",
365
+ "product_id": self.batch.picking_ids.move_line_ids[0].product_id.id,
366
+ "company_id": self.env.company.id,
367
+ }
368
+ )
369
+ )
370
+ self._scan_line_error(
371
+ self.batch.picking_ids.move_line_ids,
372
+ lot.name,
373
+ {"message_type": "error", "body": "Wrong lot."},
374
+ )
375
+
376
+ def test_scan_line_error_wrong_location(self):
377
+ """Wrong product scanned"""
378
+ self._simulate_batch_selected(self.batch, in_package=True)
379
+ location = (
380
+ self.env["stock.location"]
381
+ .sudo()
382
+ .create(
383
+ {
384
+ "name": "Wrong",
385
+ "barcode": "WRONGLOCATION",
386
+ }
387
+ )
388
+ )
389
+ self._scan_line_error(
390
+ self.batch.picking_ids.move_line_ids,
391
+ location.barcode,
392
+ {"message_type": "error", "body": "Wrong location."},
393
+ )
394
+
395
+ def test_scan_line_error_not_found(self):
396
+ """Nothing found for the barcode"""
397
+ self._simulate_batch_selected(self.batch, in_package=True)
398
+ self._scan_line_error(
399
+ self.batch.picking_ids.move_line_ids,
400
+ "NO_EXISTING_BARCODE",
401
+ {"message_type": "error", "body": "Barcode not found"},
402
+ )
@@ -0,0 +1,114 @@
1
+ # Copyright 2023 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+
4
+ from .test_cluster_picking_base import ClusterPickingLineCommonCase
5
+
6
+ # pylint: disable=missing-return
7
+
8
+
9
+ class ClusterPickingScanLineLocationOrPackFirstCase(ClusterPickingLineCommonCase):
10
+ """Tests covering the /scan_line endpoint
11
+
12
+ When the scan location or pack frist option enabled.
13
+
14
+ """
15
+
16
+ def setUp(self):
17
+ super().setUp()
18
+ self.menu.sudo().scan_location_or_pack_first = True
19
+
20
+ def _scan_line_error(self, line, scanned, message, sublocation=None):
21
+ batch = line.picking_id.batch_id
22
+ response = self.service.dispatch(
23
+ "scan_line",
24
+ params={
25
+ "picking_batch_id": batch.id,
26
+ "move_line_id": line.id,
27
+ "barcode": scanned,
28
+ "sublocation_id": sublocation.id if sublocation else None,
29
+ },
30
+ )
31
+ kw = {"sublocation": self.data.location(sublocation)} if sublocation else {}
32
+ self.assert_response(
33
+ response,
34
+ next_state="start_line",
35
+ data=self._line_data(line, scan_location_or_pack_first=True, **kw),
36
+ message=message,
37
+ )
38
+ return response
39
+
40
+ def _scan_line_ok(self, line, scanned, expected_qty_done=1, sublocation_id=None):
41
+ batch = line.picking_id.batch_id
42
+ response = self.service.dispatch(
43
+ "scan_line",
44
+ params={
45
+ "picking_batch_id": batch.id,
46
+ "move_line_id": line.id,
47
+ "barcode": scanned,
48
+ "sublocation_id": sublocation_id,
49
+ },
50
+ )
51
+ self.assert_response(
52
+ response,
53
+ next_state="scan_destination",
54
+ data=self._line_data(
55
+ line, qty_done=expected_qty_done, scan_location_or_pack_first=True
56
+ ),
57
+ )
58
+
59
+ def test_scan_line_product_ask_for_package(self):
60
+ """Check scanning the product first will request to scan the package.
61
+
62
+ This is if the line being worked on as a package.
63
+
64
+ """
65
+ self._simulate_batch_selected(self.batch, in_package=True, in_lot=False)
66
+ line = self.batch.picking_ids.move_line_ids
67
+ location = self.batch.picking_ids.location_id
68
+ self._update_qty_in_location(location, self.product_a, 2)
69
+ self._scan_line_error(
70
+ line,
71
+ line.product_id.barcode,
72
+ self.msg_store.line_has_package_scan_package(),
73
+ )
74
+
75
+ def test_scan_line_product_ask_for_location(self):
76
+ """Check scanning the product first will request to scan the location.
77
+
78
+ That is if the product on the move line is tracked by lot.
79
+
80
+ """
81
+ self._simulate_batch_selected(self.batch, in_package=False, in_lot=True)
82
+ line = self.batch.picking_ids.move_line_ids
83
+ location = self.batch.picking_ids.location_id
84
+ self._update_qty_in_location(location, self.product_a, 2)
85
+ self._scan_line_error(
86
+ line, line.product_id.barcode, self.msg_store.scan_the_location_first()
87
+ )
88
+
89
+ def test_scan_line_location_ask_for_package(self):
90
+ """Check scanning location for a line with pack will ask to scan the pack."""
91
+ self._simulate_batch_selected(self.batch, in_package=True, in_lot=False)
92
+ line = self.batch.picking_ids.move_line_ids
93
+ location = self.batch.picking_ids.location_id
94
+ self._update_qty_in_location(location, self.product_a, 2)
95
+ self._scan_line_error(
96
+ line,
97
+ line.location_id.barcode,
98
+ self.msg_store.line_has_package_scan_package(),
99
+ sublocation=location,
100
+ )
101
+
102
+ def test_scan_line_location_with_multiple_product(self):
103
+ """Check scanning a location then a product without package.
104
+
105
+ When there is multiple product in the location and the location is scanned,
106
+ The user needs to scan the product but the system does not remember the location ?
107
+
108
+ """
109
+ self._simulate_batch_selected(self.batch, in_package=False, in_lot=False)
110
+ line = self.batch.picking_ids.move_line_ids
111
+ location = self.batch.picking_ids.location_id
112
+ self._update_qty_in_location(location, self.product_a, 2)
113
+ self._update_qty_in_location(location, self.product_b, 2)
114
+ self._scan_line_ok(line, line.product_id.barcode, 1.0, location.id)