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,109 @@
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 .common import CommonCase, PickingBatchMixin
5
+
6
+ # pylint: disable=missing-return
7
+
8
+
9
+ class ClusterPickingBatchCase(CommonCase, PickingBatchMixin):
10
+ @classmethod
11
+ def setUpClassVars(cls, *args, **kwargs):
12
+ super().setUpClassVars(*args, **kwargs)
13
+ cls.menu = cls.env.ref("shopfloor.shopfloor_menu_demo_cluster_picking")
14
+ cls.profile = cls.env.ref("shopfloor_base.profile_demo_1")
15
+ cls.picking_type = cls.menu.picking_type_ids
16
+ cls.wh = cls.picking_type.warehouse_id
17
+
18
+ @classmethod
19
+ def setUpClassBaseData(cls, *args, **kwargs):
20
+ super().setUpClassBaseData(*args, **kwargs)
21
+ cls.product_a = (
22
+ cls.env["product.product"]
23
+ .sudo()
24
+ .create({"name": "Product A", "type": "product"})
25
+ )
26
+ cls.product_b = (
27
+ cls.env["product.product"]
28
+ .sudo()
29
+ .create({"name": "Product B", "type": "product"})
30
+ )
31
+ cls.batch1 = cls._create_picking_batch(
32
+ [[cls.BatchProduct(product=cls.product_a, quantity=1)]]
33
+ )
34
+ cls.batch2 = cls._create_picking_batch(
35
+ [[cls.BatchProduct(product=cls.product_a, quantity=1)]]
36
+ )
37
+ cls.batch3 = cls._create_picking_batch(
38
+ [[cls.BatchProduct(product=cls.product_a, quantity=1)]]
39
+ )
40
+ cls.batch4 = cls._create_picking_batch(
41
+ [[cls.BatchProduct(product=cls.product_b, quantity=1)]]
42
+ )
43
+ cls.batch5 = cls._create_picking_batch(
44
+ [[cls.BatchProduct(product=cls.product_b, quantity=1)]]
45
+ )
46
+ cls.batch6 = cls._create_picking_batch(
47
+ [[cls.BatchProduct(product=cls.product_b, quantity=1)]]
48
+ )
49
+ cls.all_batches = (
50
+ cls.batch1 + cls.batch2 + cls.batch3 + cls.batch4 + cls.batch5 + cls.batch6
51
+ )
52
+
53
+ def setUp(self):
54
+ super().setUp()
55
+ self.service = self.get_service(
56
+ "cluster_picking", menu=self.menu, profile=self.profile
57
+ )
58
+
59
+ def test_search_empty(self):
60
+ """No batch is available"""
61
+ # Simulate the client asking the list of picking batch
62
+ # none of the pickings are assigned, so we can't work on them
63
+ self.assertFalse(self.service._batch_picking_search())
64
+
65
+ def test_search(self):
66
+ """Return only draft batches with assigned pickings"""
67
+ pickings = self.all_batches.mapped("picking_ids")
68
+ self._fill_stock_for_moves(pickings.mapped("move_ids"))
69
+ pickings.action_assign()
70
+ self.assertTrue(all(p.state == "assigned" for p in pickings))
71
+ # we should not have done batches in list
72
+ self.batch5.state = "done"
73
+ # nor canceled batches
74
+ self.batch6.state = "cancel"
75
+ # we should not have batches in progress
76
+ self.batch4.user_id = self.env.ref("base.user_demo")
77
+ self.batch4.action_confirm()
78
+ # unless it's assigned to our user
79
+ self.batch3.user_id = self.env.user
80
+ self.batch3.action_confirm()
81
+
82
+ # Simulate the client asking the list of picking batch
83
+ res = self.service._batch_picking_search()
84
+ self.assertRecordValues(
85
+ res,
86
+ [
87
+ {
88
+ "id": self.batch1.id,
89
+ "name": self.batch1.name,
90
+ "picking_count": 1,
91
+ "move_line_count": 1,
92
+ "total_weight": 0.0,
93
+ },
94
+ {
95
+ "id": self.batch2.id,
96
+ "name": self.batch2.name,
97
+ "picking_count": 1,
98
+ "move_line_count": 1,
99
+ "total_weight": 0.0,
100
+ },
101
+ {
102
+ "id": self.batch3.id,
103
+ "name": self.batch3.name,
104
+ "picking_count": 1,
105
+ "move_line_count": 1,
106
+ "total_weight": 0.0,
107
+ },
108
+ ],
109
+ )
@@ -0,0 +1,111 @@
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 ClusterPickingChangePackLotCase(ClusterPickingCommonCase):
10
+ """Tests covering the /change_pack_lot endpoint
11
+
12
+ Only simple cases are tested to check the flow of responses on success and
13
+ error, the "change.package.lot" component is tested in its own tests.
14
+ """
15
+
16
+ @classmethod
17
+ def setUpClassBaseData(cls, *args, **kwargs):
18
+ super().setUpClassBaseData(*args, **kwargs)
19
+ cls.batch = cls._create_picking_batch(
20
+ [[cls.BatchProduct(product=cls.product_a, quantity=10)]]
21
+ )
22
+
23
+ def _test_change_pack_lot(self, line, barcode, success=True, message=None):
24
+ batch = line.picking_id.batch_id
25
+ response = self.service.dispatch(
26
+ "change_pack_lot",
27
+ params={
28
+ "picking_batch_id": batch.id,
29
+ "move_line_id": line.id,
30
+ "barcode": barcode,
31
+ "quantity": line.qty_done,
32
+ },
33
+ )
34
+ if success:
35
+ self.assert_response(
36
+ response,
37
+ message=message,
38
+ next_state="scan_destination",
39
+ data=self._line_data(line),
40
+ )
41
+ else:
42
+ self.assert_response(
43
+ response,
44
+ message=message,
45
+ next_state="change_pack_lot",
46
+ data=self._line_data(line),
47
+ )
48
+ return response
49
+
50
+ def test_change_pack_lot_change_pack_ok(self):
51
+ initial_package = self._create_package_in_location(
52
+ self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
53
+ )
54
+ self._simulate_batch_selected(self.batch, fill_stock=False)
55
+
56
+ # ensure we have our new package in the same location
57
+ new_package = self._create_package_in_location(
58
+ self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
59
+ )
60
+
61
+ line = self.batch.picking_ids.move_line_ids
62
+ self._test_change_pack_lot(
63
+ line,
64
+ new_package.name,
65
+ success=True,
66
+ message=self.service.msg_store.package_replaced_by_package(
67
+ initial_package, new_package
68
+ ),
69
+ )
70
+
71
+ self.assertRecordValues(
72
+ line,
73
+ [
74
+ {
75
+ "package_id": new_package.id,
76
+ "result_package_id": new_package.id,
77
+ "reserved_qty": 10.0,
78
+ }
79
+ ],
80
+ )
81
+ self.assertRecordValues(line.package_level_id, [{"package_id": new_package.id}])
82
+
83
+ def test_change_pack_lot_change_lot_ok(self):
84
+ initial_lot = self._create_lot(self.product_a)
85
+ self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
86
+ self._simulate_batch_selected(self.batch, fill_stock=False)
87
+ line = self.batch.picking_ids.move_line_ids
88
+ source_location = line.location_id
89
+ new_lot = self._create_lot(self.product_a)
90
+ # ensure we have our new package in the same location
91
+ self._update_qty_in_location(source_location, line.product_id, 10, lot=new_lot)
92
+ self._test_change_pack_lot(
93
+ line,
94
+ new_lot.name,
95
+ success=True,
96
+ message=self.service.msg_store.lot_replaced_by_lot(initial_lot, new_lot),
97
+ )
98
+ self.assertRecordValues(line, [{"lot_id": new_lot.id}])
99
+
100
+ def test_change_pack_lot_change_error(self):
101
+ initial_lot = self._create_lot(self.product_a)
102
+ self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
103
+ self._simulate_batch_selected(self.batch, fill_stock=False)
104
+ line = self.batch.picking_ids.move_line_ids
105
+ # ensure we have our new package in the same location
106
+ self._test_change_pack_lot(
107
+ line,
108
+ "NOT_FOUND",
109
+ success=False,
110
+ message=self.service.msg_store.no_package_or_lot_for_barcode("NOT_FOUND"),
111
+ )
@@ -0,0 +1,98 @@
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 ClusterPickingIsZeroCase(ClusterPickingCommonCase):
10
+ """Tests covering the /is_zero endpoint
11
+
12
+ After a line has been scanned, if the location is empty, the
13
+ client application is redirected to the "zero_check" state,
14
+ where the user has to confirm or not that the location is empty.
15
+ When the location is empty, there is nothing to do, but when it
16
+ in fact not empty, a draft inventory must be created for the
17
+ product so someone can verify.
18
+ """
19
+
20
+ @classmethod
21
+ def setUpClassBaseData(cls, *args, **kwargs):
22
+ super().setUpClassBaseData(*args, **kwargs)
23
+ cls.batch = cls._create_picking_batch(
24
+ [
25
+ [
26
+ cls.BatchProduct(product=cls.product_a, quantity=10),
27
+ cls.BatchProduct(product=cls.product_b, quantity=10),
28
+ ]
29
+ ]
30
+ )
31
+ cls.picking = cls.batch.picking_ids
32
+ cls._simulate_batch_selected(cls.batch)
33
+
34
+ cls.line = cls.picking.move_line_ids[0]
35
+ cls.next_line = cls.picking.move_line_ids[1]
36
+ cls.bin1 = cls.env["stock.quant.package"].create({})
37
+ cls._update_qty_in_location(
38
+ cls.line.location_id, cls.line.product_id, cls.line.reserved_uom_qty
39
+ )
40
+ # we already scan and put the first line in bin1, at this point the
41
+ # system see the location is empty and reach "zero_check"
42
+ cls._set_dest_package_and_done(cls.line, cls.bin1)
43
+
44
+ def test_is_zero_is_empty(self):
45
+ """call /is_zero confirming it's empty"""
46
+ response = self.service.dispatch(
47
+ "is_zero",
48
+ params={
49
+ "picking_batch_id": self.batch.id,
50
+ "move_line_id": self.line.id,
51
+ "zero": True,
52
+ },
53
+ )
54
+ self.assert_response(
55
+ response,
56
+ next_state="start_line",
57
+ data=self._line_data(self.next_line),
58
+ message={
59
+ "message_type": "success",
60
+ "body": "{} {} put in {}".format(
61
+ self.line.qty_done,
62
+ self.line.product_id.display_name,
63
+ self.bin1.name,
64
+ ),
65
+ },
66
+ )
67
+
68
+ def test_is_zero_is_not_empty(self):
69
+ """call /is_zero not confirming it's empty"""
70
+ response = self.service.dispatch(
71
+ "is_zero",
72
+ params={
73
+ "picking_batch_id": self.batch.id,
74
+ "move_line_id": self.line.id,
75
+ "zero": False,
76
+ },
77
+ )
78
+ quant = self.env["stock.quant"].search(
79
+ [
80
+ ("location_id", "=", self.line.location_id.id),
81
+ ("product_id", "=", self.line.product_id.id),
82
+ ("inventory_quantity_set", "=", True),
83
+ ]
84
+ )
85
+ self.assertTrue(quant)
86
+ self.assert_response(
87
+ response,
88
+ next_state="start_line",
89
+ data=self._line_data(self.next_line),
90
+ message={
91
+ "message_type": "success",
92
+ "body": "{} {} put in {}".format(
93
+ self.line.qty_done,
94
+ self.line.product_id.display_name,
95
+ self.bin1.name,
96
+ ),
97
+ },
98
+ )
@@ -0,0 +1,376 @@
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 ClusterPickingScanDestinationPackCase(ClusterPickingCommonCase):
10
+ """Tests covering the /scan_destination_pack endpoint
11
+
12
+ After a batch has been selected and the user confirmed they are
13
+ working on it, user picked the good, now they scan the location
14
+ destination.
15
+ """
16
+
17
+ @classmethod
18
+ def setUpClassBaseData(cls, *args, **kwargs):
19
+ super().setUpClassBaseData(*args, **kwargs)
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_ok(self):
42
+ """Happy path for scan destination package
43
+
44
+ It sets the line in the pack for the full qty
45
+ """
46
+ line = self.batch.picking_ids.move_line_ids[0]
47
+ next_line = self.batch.picking_ids.move_line_ids[1]
48
+ qty_done = line.reserved_uom_qty
49
+ response = self.service.dispatch(
50
+ "scan_destination_pack",
51
+ params={
52
+ "picking_batch_id": self.batch.id,
53
+ "move_line_id": line.id,
54
+ "barcode": self.bin1.name,
55
+ "quantity": qty_done,
56
+ },
57
+ )
58
+ self.assertRecordValues(
59
+ line, [{"qty_done": qty_done, "result_package_id": self.bin1.id}]
60
+ )
61
+ self.assert_response(
62
+ response,
63
+ next_state="start_line",
64
+ data=self._line_data(next_line),
65
+ message={
66
+ "message_type": "success",
67
+ "body": "{} {} put in {}".format(
68
+ line.qty_done, line.product_id.display_name, self.bin1.name
69
+ ),
70
+ },
71
+ )
72
+
73
+ def test_scan_destination_pack_ok_last_line(self):
74
+ """Happy path for scan destination package
75
+
76
+ It sets the line in the pack for the full qty
77
+ """
78
+ self._set_dest_package_and_done(self.one_line_picking.move_line_ids, self.bin1)
79
+ self._set_dest_package_and_done(
80
+ self.two_lines_picking.move_line_ids[0], self.bin2
81
+ )
82
+ # this is the only remaining line to pick
83
+ line = self.two_lines_picking.move_line_ids[1]
84
+ qty_done = line.reserved_uom_qty
85
+ response = self.service.dispatch(
86
+ "scan_destination_pack",
87
+ params={
88
+ "picking_batch_id": self.batch.id,
89
+ "move_line_id": line.id,
90
+ "barcode": self.bin2.name,
91
+ "quantity": qty_done,
92
+ },
93
+ )
94
+ self.assertRecordValues(
95
+ line, [{"qty_done": qty_done, "result_package_id": self.bin2.id}]
96
+ )
97
+ data = self._data_for_batch(self.batch, self.packing_location)
98
+ self.assert_response(
99
+ response,
100
+ # they reach the same destination so next state unload_all
101
+ next_state="unload_all",
102
+ data=data,
103
+ )
104
+
105
+ def test_scan_destination_pack_not_empty_same_picking(self):
106
+ """Scan a destination package with move lines of same picking"""
107
+ line1 = self.two_lines_picking.move_line_ids[0]
108
+ line2 = self.two_lines_picking.move_line_ids[1]
109
+ # we already scan and put the first line in bin1
110
+ self._set_dest_package_and_done(line1, self.bin1)
111
+ response = self.service.dispatch(
112
+ "scan_destination_pack",
113
+ params={
114
+ "picking_batch_id": self.batch.id,
115
+ "move_line_id": line2.id,
116
+ # this bin is used for the same picking, should be allowed
117
+ "barcode": self.bin1.name,
118
+ "quantity": line2.reserved_uom_qty,
119
+ },
120
+ )
121
+ self.assert_response(
122
+ response,
123
+ next_state="start_line",
124
+ # we did not pick this line, so it should go there
125
+ data=self._line_data(self.one_line_picking.move_line_ids),
126
+ message=self.ANY,
127
+ )
128
+
129
+ def test_scan_destination_pack_not_empty_different_picking(self):
130
+ """Scan a destination package with move lines of other picking"""
131
+ # do as if the user already picked the first good (for another picking)
132
+ # and put it in bin1
133
+ self._set_dest_package_and_done(self.one_line_picking.move_line_ids, self.bin1)
134
+ line = self.two_lines_picking.move_line_ids[0]
135
+ response = self.service.dispatch(
136
+ "scan_destination_pack",
137
+ params={
138
+ "picking_batch_id": self.batch.id,
139
+ "move_line_id": line.id,
140
+ # this bin is used for the other picking
141
+ "barcode": self.bin1.name,
142
+ "quantity": line.reserved_uom_qty,
143
+ },
144
+ )
145
+ self.assertRecordValues(line, [{"qty_done": 0, "result_package_id": False}])
146
+ self.assert_response(
147
+ response,
148
+ next_state="scan_destination",
149
+ data=self._line_data(line, qty_done=10.0),
150
+ message={
151
+ "message_type": "error",
152
+ "body": "The destination bin {} is not empty,"
153
+ " please take another.".format(self.bin1.name),
154
+ },
155
+ )
156
+
157
+ def test_scan_destination_pack_not_empty_multi_pick_allowed(self):
158
+ """Scan a destination package with move lines of other picking"""
159
+ # do as if the user already picked the first good (for another picking)
160
+ # and put it in bin1
161
+ self.menu.sudo().write(
162
+ {"unload_package_at_destination": True, "multiple_move_single_pack": True}
163
+ )
164
+ self._set_dest_package_and_done(self.one_line_picking.move_line_ids, self.bin1)
165
+ line = self.two_lines_picking.move_line_ids[0]
166
+ self.service.dispatch(
167
+ "scan_destination_pack",
168
+ params={
169
+ "picking_batch_id": self.batch.id,
170
+ "move_line_id": line.id,
171
+ # this bin is used for the other picking
172
+ "barcode": self.bin1.name,
173
+ "quantity": line.reserved_uom_qty,
174
+ },
175
+ )
176
+ # Since `multiple_move_single_pack` is enabled, assigning `bin` should be ok
177
+ new_line = self.two_lines_picking.move_line_ids - line
178
+ self.assertRecordValues(
179
+ line,
180
+ [
181
+ {
182
+ "qty_done": 10,
183
+ "result_package_id": self.bin1.id,
184
+ "reserved_uom_qty": 10,
185
+ }
186
+ ],
187
+ )
188
+ self.assertRecordValues(
189
+ new_line,
190
+ [{"qty_done": 0, "result_package_id": False, "reserved_uom_qty": 10}],
191
+ )
192
+
193
+ def test_scan_destination_pack_bin_not_found(self):
194
+ """Scan a destination package that do not exist"""
195
+ line = self.one_line_picking.move_line_ids
196
+ response = self.service.dispatch(
197
+ "scan_destination_pack",
198
+ params={
199
+ "picking_batch_id": self.batch.id,
200
+ "move_line_id": line.id,
201
+ # this bin is used for the other picking
202
+ "barcode": "⌿",
203
+ "quantity": line.reserved_uom_qty,
204
+ },
205
+ )
206
+ line_data = self._line_data(line)
207
+ line_data["qty_done"] = 10
208
+ self.assert_response(
209
+ response,
210
+ next_state="scan_destination",
211
+ data=line_data,
212
+ message={
213
+ "message_type": "error",
214
+ "body": "Bin {} doesn't exist".format("⌿"),
215
+ },
216
+ )
217
+
218
+ def test_scan_destination_pack_quantity_more(self):
219
+ """Pick more units than expected"""
220
+ line = self.one_line_picking.move_line_ids
221
+ response = self.service.dispatch(
222
+ "scan_destination_pack",
223
+ params={
224
+ "picking_batch_id": self.batch.id,
225
+ "move_line_id": line.id,
226
+ "barcode": self.bin1.name,
227
+ "quantity": line.reserved_uom_qty + 1,
228
+ },
229
+ )
230
+ self.assert_response(
231
+ response,
232
+ next_state="scan_destination",
233
+ data=self._line_data(line, qty_done=11.0),
234
+ message={
235
+ "message_type": "error",
236
+ "body": "You must not pick more than {} units.".format(
237
+ line.reserved_uom_qty
238
+ ),
239
+ },
240
+ )
241
+
242
+ def test_scan_destination_pack_quantity_less(self):
243
+ """Pick less units than expected"""
244
+ line = self.one_line_picking.move_line_ids
245
+ quant = self.env["stock.quant"].search(
246
+ [
247
+ ("location_id", "=", line.location_id.id),
248
+ ("product_id", "=", line.product_id.id),
249
+ ]
250
+ )
251
+ quant.ensure_one()
252
+ self.assertRecordValues(quant, [{"quantity": 40.0, "reserved_quantity": 20.0}])
253
+
254
+ # when we pick less quantity than expected, the line is split
255
+ # and the user is proposed to pick the next line for the remaining
256
+ # quantity
257
+ response = self.service.dispatch(
258
+ "scan_destination_pack",
259
+ params={
260
+ "picking_batch_id": self.batch.id,
261
+ "move_line_id": line.id,
262
+ "barcode": self.bin1.name,
263
+ "quantity": line.reserved_uom_qty - 3,
264
+ },
265
+ )
266
+ new_line = self.one_line_picking.move_line_ids - line
267
+
268
+ self.assert_response(
269
+ response,
270
+ next_state="start_line",
271
+ data=self._line_data(new_line),
272
+ message={
273
+ "message_type": "success",
274
+ "body": "{} {} put in {}".format(
275
+ line.qty_done, line.product_id.display_name, self.bin1.name
276
+ ),
277
+ },
278
+ )
279
+
280
+ self.assertRecordValues(
281
+ line,
282
+ [{"qty_done": 7, "result_package_id": self.bin1.id, "reserved_uom_qty": 7}],
283
+ )
284
+ self.assertRecordValues(
285
+ new_line,
286
+ [{"qty_done": 0, "result_package_id": False, "reserved_uom_qty": 3}],
287
+ )
288
+ # the reserved quantity on the quant must stay the same
289
+ self.assertRecordValues(quant, [{"quantity": 40.0, "reserved_quantity": 20.0}])
290
+
291
+ def test_scan_destination_pack_zero_check_activated(self):
292
+ """Location will be emptied, have to go to zero check"""
293
+ # ensure that the location used for the test will contain only what we want
294
+ self.zero_check_location = (
295
+ self.env["stock.location"]
296
+ .sudo()
297
+ .create(
298
+ {
299
+ "name": "ZeroCheck",
300
+ "location_id": self.stock_location.id,
301
+ "barcode": "ZEROCHECK",
302
+ }
303
+ )
304
+ )
305
+ line = self.one_line_picking.move_line_ids
306
+ location, product, qty = (
307
+ self.zero_check_location,
308
+ line.product_id,
309
+ line.reserved_uom_qty,
310
+ )
311
+ self.one_line_picking.do_unreserve()
312
+
313
+ # ensure we have activated the zero check
314
+ self.one_line_picking.picking_type_id.sudo().shopfloor_zero_check = True
315
+ # Update the quantity in the location to be equal to the line's
316
+ # so when scan_destination_pack sets the qty_done, the planned
317
+ # qty should be zero and trigger a zero check
318
+ self._update_qty_in_location(location, product, qty)
319
+ # Reserve goods (now the move line has the expected source location)
320
+ self.one_line_picking.move_ids.location_id = location
321
+ self.one_line_picking.action_assign()
322
+ line = self.one_line_picking.move_line_ids
323
+ response = self.service.dispatch(
324
+ "scan_destination_pack",
325
+ params={
326
+ "picking_batch_id": self.batch.id,
327
+ "move_line_id": line.id,
328
+ "barcode": self.bin1.name,
329
+ "quantity": line.reserved_uom_qty,
330
+ },
331
+ )
332
+
333
+ self.assert_response(
334
+ response,
335
+ next_state="zero_check",
336
+ data={
337
+ "id": line.id,
338
+ "location_src": self.data.location(line.location_id),
339
+ "batch": self.data.picking_batch(self.batch),
340
+ },
341
+ )
342
+
343
+ def test_scan_destination_pack_zero_check_disabled(self):
344
+ """Location will be emptied, no zero check, continue"""
345
+ line = self.one_line_picking.move_line_ids
346
+ # ensure we have deactivated the zero check
347
+ self.one_line_picking.picking_type_id.sudo().shopfloor_zero_check = False
348
+ # Update the quantity in the location to be equal to the line's
349
+ # so when scan_destination_pack sets the qty_done, the planned
350
+ # qty should be zero and trigger a zero check
351
+ self._update_qty_in_location(
352
+ line.location_id, line.product_id, line.reserved_uom_qty
353
+ )
354
+ response = self.service.dispatch(
355
+ "scan_destination_pack",
356
+ params={
357
+ "picking_batch_id": self.batch.id,
358
+ "move_line_id": line.id,
359
+ "barcode": self.bin1.name,
360
+ "quantity": line.reserved_uom_qty,
361
+ },
362
+ )
363
+
364
+ next_line = self.two_lines_picking.move_line_ids[0]
365
+ # continue to the next one, no zero check
366
+ self.assert_response(
367
+ response,
368
+ next_state="start_line",
369
+ data=self._line_data(next_line),
370
+ message={
371
+ "message_type": "success",
372
+ "body": "{} {} put in {}".format(
373
+ line.qty_done, line.product_id.display_name, self.bin1.name
374
+ ),
375
+ },
376
+ )