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,359 @@
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_location_content_transfer_base import LocationContentTransferCommonCase
5
+
6
+ # pylint: disable=missing-return
7
+
8
+
9
+ class TestLocationContentTransferStart(LocationContentTransferCommonCase):
10
+ """Tests for start state and recover
11
+
12
+ Endpoints:
13
+
14
+ * /start_or_recover
15
+ * /scan_location
16
+ """
17
+
18
+ @classmethod
19
+ def setUpClassBaseData(cls):
20
+ super().setUpClassBaseData()
21
+ products = cls.product_a + cls.product_b + cls.product_c + cls.product_d
22
+ for product in products:
23
+ cls.env["stock.putaway.rule"].sudo().create(
24
+ {
25
+ "product_id": product.id,
26
+ "location_in_id": cls.stock_location.id,
27
+ "location_out_id": cls.shelf1.id,
28
+ }
29
+ )
30
+
31
+ cls.picking1 = picking1 = cls._create_picking(
32
+ lines=[(cls.product_a, 10), (cls.product_b, 10)]
33
+ )
34
+ cls.picking2 = picking2 = cls._create_picking(
35
+ lines=[(cls.product_c, 10), (cls.product_d, 10)]
36
+ )
37
+ cls.pickings = picking1 | picking2
38
+
39
+ cls.content_loc2 = (
40
+ cls.env["stock.location"]
41
+ .sudo()
42
+ .create(
43
+ {
44
+ "name": "Content Location 2",
45
+ "barcode": "Content2",
46
+ "location_id": cls.picking_type.default_location_src_id.id,
47
+ }
48
+ )
49
+ )
50
+ cls._fill_stock_for_moves(
51
+ picking1.move_ids, in_package=True, location=cls.content_loc
52
+ )
53
+ cls._fill_stock_for_moves(picking2.move_ids[0], location=cls.content_loc)
54
+ cls._fill_stock_for_moves(picking2.move_ids[1], location=cls.content_loc2)
55
+ cls.pickings.action_assign()
56
+ cls.move_lines = cls.pickings.move_line_ids
57
+
58
+ def test_start_fresh(self):
59
+ """Start a fresh session when there is no transfer to recover"""
60
+ response = self.service.dispatch("start_or_recover", params={})
61
+ self.assert_response(response, next_state="scan_location")
62
+
63
+ def test_start_recover_destination_all(self):
64
+ """Recover transfers, all move lines have the same destination"""
65
+ self._simulate_pickings_selected(self.picking1)
66
+ # all lines go to the same destination (shelf1)
67
+ self.assertEqual(len(self.picking1.mapped("move_line_ids.location_dest_id")), 1)
68
+
69
+ response = self.service.dispatch("start_or_recover", params={})
70
+ self.assert_response_scan_destination_all(
71
+ response,
72
+ self.picking1,
73
+ message=self.service.msg_store.recovered_previous_session(),
74
+ )
75
+
76
+ def test_start_recover_destination_single(self):
77
+ """Recover transfers, at least one move line has a different destination"""
78
+ self._simulate_pickings_selected(self.pickings)
79
+ self.picking1.package_level_ids.location_dest_id = self.shelf2
80
+ # we have different destinations
81
+ self.assertEqual(len(self.pickings.mapped("move_line_ids.location_dest_id")), 2)
82
+ response = self.service.dispatch("start_or_recover", params={})
83
+ self.assert_response_start_single(
84
+ response,
85
+ self.pickings,
86
+ message=self.service.msg_store.recovered_previous_session(),
87
+ )
88
+
89
+ def test_scan_location_not_found(self):
90
+ """Scan a location with content to transfer, barcode not found"""
91
+ response = self.service.dispatch(
92
+ "scan_location", params={"barcode": "NOT_FOUND"}
93
+ )
94
+ self.assert_response_start(
95
+ response, message=self.service.msg_store.barcode_not_found()
96
+ )
97
+
98
+ def test_scan_location_find_content_destination_all(self):
99
+ """Scan a location with content to transfer, all dest. identical"""
100
+ # all lines go to the same destination (shelf1)
101
+ self.assertEqual(len(self.move_lines.location_dest_id), 1)
102
+ response = self.service.dispatch(
103
+ "scan_location", params={"barcode": self.content_loc.barcode}
104
+ )
105
+ processed_move_lines = self.move_lines.filtered(
106
+ lambda line: line.location_id == self.content_loc
107
+ )
108
+ processed_pickings = processed_move_lines.picking_id
109
+ self.assertTrue(processed_pickings != self.pickings)
110
+ self.assert_response_scan_destination_all(response, processed_pickings)
111
+ self.assertRecordValues(
112
+ processed_pickings, [{"user_id": self.env.uid}, {"user_id": self.env.uid}]
113
+ )
114
+ self.assertRecordValues(
115
+ processed_move_lines,
116
+ [
117
+ {"qty_done": 10.0},
118
+ {"qty_done": 10.0},
119
+ {"qty_done": 10.0},
120
+ ],
121
+ )
122
+ self.assertRecordValues(
123
+ processed_pickings.package_level_ids, [{"is_done": True}]
124
+ )
125
+
126
+ def test_scan_location_find_content_destination_single(self):
127
+ """Scan a location with content to transfer, different destinations"""
128
+ self.picking1.package_level_ids.location_dest_id = self.shelf2
129
+ # we have different destinations
130
+ self.assertEqual(len(self.pickings.mapped("move_line_ids.location_dest_id")), 2)
131
+ response = self.service.dispatch(
132
+ "scan_location", params={"barcode": self.content_loc.barcode}
133
+ )
134
+ processed_move_lines = self.move_lines.filtered(
135
+ lambda line: line.location_id == self.content_loc
136
+ )
137
+ processed_pickings = processed_move_lines.picking_id
138
+ self.assert_response_start_single(response, processed_pickings)
139
+ self.assertRecordValues(
140
+ processed_pickings, [{"user_id": self.env.uid}, {"user_id": self.env.uid}]
141
+ )
142
+ self.assertRecordValues(
143
+ processed_move_lines,
144
+ [
145
+ {"qty_done": 10.0},
146
+ {"qty_done": 10.0},
147
+ {"qty_done": 10.0},
148
+ ],
149
+ )
150
+ self.assertRecordValues(
151
+ processed_pickings.package_level_ids, [{"is_done": True}]
152
+ )
153
+
154
+ def test_scan_location_different_picking_type(self):
155
+ """Content has different picking types, can't move"""
156
+ picking_other_type = self._create_picking(
157
+ picking_type=self.wh.pick_type_id, lines=[(self.product_a, 10)]
158
+ )
159
+ self._fill_stock_for_moves(
160
+ picking_other_type.move_ids, location=self.content_loc
161
+ )
162
+ picking_other_type.action_assign()
163
+
164
+ response = self.service.dispatch(
165
+ "scan_location", params={"barcode": self.content_loc.barcode}
166
+ )
167
+ self.assert_response_start(
168
+ response,
169
+ message={
170
+ "message_type": "error",
171
+ "body": "This location content can't be moved at once.",
172
+ },
173
+ )
174
+
175
+
176
+ class LocationContentTransferStartSpecialCase(LocationContentTransferCommonCase):
177
+ """Tests for start state and recover (special cases without setup)
178
+
179
+ Endpoints:
180
+
181
+ * /start_or_recover
182
+ * /scan_location
183
+ """
184
+
185
+ def test_scan_location_wrong_picking_type_error(self):
186
+ """Content has different picking type than menu"""
187
+ picking = self._create_picking(
188
+ picking_type=self.wh.pick_type_id,
189
+ lines=[(self.product_a, 10), (self.product_b, 10)],
190
+ )
191
+ self._fill_stock_for_moves(
192
+ picking.move_ids, in_package=True, location=self.content_loc
193
+ )
194
+ picking.action_assign()
195
+ response = self.service.dispatch(
196
+ "scan_location", params={"barcode": self.content_loc.barcode}
197
+ )
198
+ self.assert_response_start(
199
+ response,
200
+ message={
201
+ "message_type": "error",
202
+ "body": "You cannot move this using this menu.",
203
+ },
204
+ )
205
+
206
+ def test_scan_location_wrong_picking_type_allow_unreserve_ok(self):
207
+ """Content has different picking type than menu, option to unreserve
208
+
209
+ The content must be unreserved, new moves created and the previous
210
+ content re-reserved.
211
+ """
212
+ self.menu.sudo().allow_unreserve_other_moves = True
213
+
214
+ picking = self._create_picking(
215
+ picking_type=self.wh.pick_type_id,
216
+ lines=[(self.product_a, 10), (self.product_b, 10)],
217
+ )
218
+ self._fill_stock_for_moves(
219
+ picking.move_ids, in_package=True, location=self.content_loc
220
+ )
221
+ picking.action_assign()
222
+ # place goods in shelf1 to ensure the original picking can take goods here
223
+ other_pack_a = self.env["stock.quant.package"].create({})
224
+ other_pack_b = self.env["stock.quant.package"].create({})
225
+ self._update_qty_in_location(
226
+ self.shelf1, self.product_a, 10, package=other_pack_a
227
+ )
228
+ self._update_qty_in_location(
229
+ self.shelf1, self.product_b, 10, package=other_pack_b
230
+ )
231
+ response = self.service.dispatch(
232
+ "scan_location", params={"barcode": self.content_loc.barcode}
233
+ )
234
+ new_picking = self.env["stock.picking"].search(
235
+ [("picking_type_id", "=", self.picking_type.id)]
236
+ )
237
+ self.assertEqual(len(new_picking), 1)
238
+ self.assert_response_scan_destination_all(response, new_picking)
239
+ self.assertRecordValues(new_picking, [{"user_id": self.env.uid}])
240
+ self.assertRecordValues(
241
+ new_picking.move_line_ids,
242
+ [{"qty_done": 10.0}, {"qty_done": 10.0}],
243
+ )
244
+ self.assertRecordValues(new_picking.package_level_ids, [{"is_done": True}])
245
+
246
+ # the original picking must be reserved again, should have taken the goods
247
+ # of shelf1
248
+ self.assertRecordValues(
249
+ picking.move_line_ids,
250
+ [
251
+ {
252
+ "qty_done": 0.0,
253
+ "location_id": self.shelf1.id,
254
+ "package_id": other_pack_a.id,
255
+ },
256
+ {
257
+ "qty_done": 0.0,
258
+ "location_id": self.shelf1.id,
259
+ "package_id": other_pack_b.id,
260
+ },
261
+ ],
262
+ )
263
+
264
+ def test_scan_location_wrong_picking_type_allow_unreserve_empty(self):
265
+ """Content has different picking type than menu, option to unreserve
266
+
267
+ There is no move line of another picking type to unreserve.
268
+ """
269
+ self.menu.sudo().allow_unreserve_other_moves = True
270
+ response = self.service.dispatch(
271
+ "scan_location", params={"barcode": self.content_loc.barcode}
272
+ )
273
+ self.assert_response_start(
274
+ response,
275
+ message=self.service.msg_store.location_empty(self.content_loc),
276
+ )
277
+
278
+ def test_scan_location_wrong_picking_type_allow_unreserve_error(self):
279
+ """Content has different picking type than menu, option to unreserve
280
+
281
+ If quantity has been partially picked on the existing transfer, prevent
282
+ to unreserve them.
283
+ """
284
+ self.menu.sudo().allow_unreserve_other_moves = True
285
+
286
+ picking = self._create_picking(
287
+ picking_type=self.wh.pick_type_id,
288
+ lines=[(self.product_a, 10), (self.product_b, 10)],
289
+ )
290
+ self._fill_stock_for_moves(
291
+ picking.move_ids, in_package=True, location=self.content_loc
292
+ )
293
+ picking.action_assign()
294
+ # a user picked qty
295
+ picking.move_line_ids[0].qty_done = 10
296
+ response = self.service.dispatch(
297
+ "scan_location", params={"barcode": self.content_loc.barcode}
298
+ )
299
+ self.assert_response_start(
300
+ response,
301
+ message=self.service.msg_store.picking_already_started_in_location(picking),
302
+ )
303
+ # check that the original moves are still assigned
304
+ self.assertRecordValues(
305
+ picking.move_ids, [{"state": "assigned"}, {"state": "assigned"}]
306
+ )
307
+
308
+ def test_scan_location_create_moves(self):
309
+ """The scanned location has no move lines but has some quants to move."""
310
+ picking_type = self.menu.picking_type_ids
311
+ # product_a alone
312
+ self.env["stock.quant"]._update_available_quantity(
313
+ self.product_a,
314
+ self.content_loc,
315
+ 10,
316
+ )
317
+ # product_b in a package
318
+ package = self.env["stock.quant.package"].create({})
319
+ self.env["stock.quant"]._update_available_quantity(
320
+ self.product_b, self.content_loc, 10, package_id=package
321
+ )
322
+ # product_c & product_d in a package
323
+ package2 = self.env["stock.quant.package"].create({})
324
+ self.env["stock.quant"]._update_available_quantity(
325
+ self.product_c, self.content_loc, 5, package_id=package2
326
+ )
327
+ self.env["stock.quant"]._update_available_quantity(
328
+ self.product_d, self.content_loc, 5, package_id=package2
329
+ )
330
+ response = self.service.dispatch(
331
+ "scan_location", params={"barcode": self.content_loc.barcode}
332
+ )
333
+ picking = self.env["stock.picking"].search(
334
+ [("picking_type_id", "=", picking_type.id)]
335
+ )
336
+ self.assertEqual(len(picking), 1)
337
+ self.assert_response_scan_destination_all(response, picking)
338
+ move_line_id = response["data"]["scan_destination_all"]["move_lines"][0]["id"]
339
+ package_levels = response["data"]["scan_destination_all"]["package_levels"]
340
+ self.assertIn(move_line_id, picking.move_line_ids.ids)
341
+ self.assertEqual(package_levels[0]["id"], picking.package_level_ids[0].id)
342
+ self.assertEqual(package_levels[0]["package_src"]["id"], package.id)
343
+ self.assertEqual(package_levels[1]["id"], picking.package_level_ids[1].id)
344
+ self.assertEqual(package_levels[1]["package_src"]["id"], package2.id)
345
+ # product_a in a move line without package
346
+ self.assertEqual(
347
+ picking.move_line_ids_without_package.mapped("product_id"), self.product_a
348
+ )
349
+ # all other products are in package levels
350
+ self.assertEqual(
351
+ picking.package_level_ids.mapped("package_id.quant_ids.product_id"),
352
+ self.product_b | self.product_c | self.product_d,
353
+ )
354
+ # all products are in move lines
355
+ self.assertEqual(
356
+ picking.move_line_ids.mapped("product_id"),
357
+ self.product_a | self.product_b | self.product_c | self.product_d,
358
+ )
359
+ self.assertEqual(picking.state, "assigned")
@@ -0,0 +1,261 @@
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 odoo.addons.shopfloor_base.tests.common_misc import MenuTestMixin
5
+
6
+ from .common import CommonCase
7
+
8
+ # pylint: disable=missing-return
9
+
10
+
11
+ class CommonMenuCase(CommonCase, MenuTestMixin):
12
+ @classmethod
13
+ def setUpClassVars(cls, *args, **kwargs):
14
+ super().setUpClassVars(*args, **kwargs)
15
+ ref = cls.env.ref
16
+ profile1 = ref("shopfloor_base.profile_demo_1")
17
+ cls.wms_profile = ref("shopfloor.profile_demo_1")
18
+ cls.wms_profile2 = ref("shopfloor.profile_demo_2")
19
+ menu_xid_pref = "shopfloor.shopfloor_menu_demo_"
20
+ cls.menu_items = (
21
+ ref(menu_xid_pref + "single_pallet_transfer")
22
+ | ref(menu_xid_pref + "zone_picking")
23
+ | ref(menu_xid_pref + "cluster_picking")
24
+ | ref(menu_xid_pref + "checkout")
25
+ | ref(menu_xid_pref + "delivery")
26
+ | ref(menu_xid_pref + "location_content_transfer")
27
+ )
28
+ # Isolate menu items
29
+ cls.env["shopfloor.menu"].search(
30
+ [("id", "not in", cls.menu_items.ids)]
31
+ ).sudo().write({"profile_id": profile1.id})
32
+
33
+ def _data_for_menu_item(self, menu, **kw):
34
+ data = super()._data_for_menu_item(menu, **kw)
35
+ if menu.picking_type_ids:
36
+ data.update(
37
+ {
38
+ "picking_types": [
39
+ {"id": picking_type.id, "name": picking_type.name}
40
+ for picking_type in menu.picking_type_ids
41
+ ],
42
+ }
43
+ )
44
+ expected_counters = kw.get("expected_counters") or {}
45
+ counters = expected_counters.get(
46
+ menu.id,
47
+ {
48
+ "lines_count": 0,
49
+ "picking_count": 0,
50
+ "priority_lines_count": 0,
51
+ "priority_picking_count": 0,
52
+ },
53
+ )
54
+ data.update(counters)
55
+ return data
56
+
57
+
58
+ class MenuCountersCommonCase(CommonMenuCase):
59
+ @classmethod
60
+ def setUpClassVars(cls, *args, **kwargs):
61
+ super().setUpClassVars(*args, **kwargs)
62
+ cls.menu1 = cls.env.ref("shopfloor.shopfloor_menu_demo_zone_picking")
63
+ cls.menu2 = cls.env.ref("shopfloor.shopfloor_menu_demo_cluster_picking")
64
+ cls.menu1_picking_type = cls.menu1.picking_type_ids[0]
65
+ cls.menu2_picking_type = cls.menu2.picking_type_ids[0]
66
+ cls.wms_profile = cls.env.ref("shopfloor.profile_demo_1")
67
+ cls.wms_profile2 = cls.env.ref("shopfloor.profile_demo_2")
68
+
69
+ @classmethod
70
+ def setUpClassBaseData(cls, *args, **kwargs):
71
+ super().setUpClassBaseData(*args, **kwargs)
72
+ # Setup some data to simulate good amount of operations
73
+ # to be worked on w/ the app which will be reflected in the menu counters.
74
+ cls.packing_location.sudo().active = True
75
+ # We want to limit the tests to a dedicated location in Stock/ to not
76
+ # be bothered with pickings brought by demo data
77
+ cls.zone_location1 = (
78
+ cls.env["stock.location"]
79
+ .sudo()
80
+ .create(
81
+ {
82
+ "name": "Zone location 1",
83
+ "location_id": cls.stock_location.id,
84
+ "barcode": "ZONE_LOCATION_1",
85
+ }
86
+ )
87
+ )
88
+ cls.zone_location2 = (
89
+ cls.env["stock.location"]
90
+ .sudo()
91
+ .create(
92
+ {
93
+ "name": "Zone location 2",
94
+ "location_id": cls.stock_location.id,
95
+ "barcode": "ZONE_LOCATION_2",
96
+ }
97
+ )
98
+ )
99
+ # Set default location for our picking types
100
+ cls.menu1_picking_type.sudo().default_location_src_id = cls.zone_location1
101
+ cls.menu2_picking_type.sudo().default_location_src_id = cls.zone_location2
102
+ cls.zone_sublocation1 = (
103
+ cls.env["stock.location"]
104
+ .sudo()
105
+ .create(
106
+ {
107
+ "name": "Zone sub-location 1",
108
+ "location_id": cls.zone_location1.id,
109
+ "barcode": "ZONE_SUBLOCATION_1",
110
+ }
111
+ )
112
+ )
113
+ cls.zone_sublocation2 = (
114
+ cls.env["stock.location"]
115
+ .sudo()
116
+ .create(
117
+ {
118
+ "name": "Zone sub-location 2",
119
+ "location_id": cls.zone_location2.id,
120
+ "barcode": "ZONE_SUBLOCATION_2",
121
+ }
122
+ )
123
+ )
124
+ cls.zone_sublocation3 = (
125
+ cls.env["stock.location"]
126
+ .sudo()
127
+ .create(
128
+ {
129
+ "name": "Zone sub-location 3",
130
+ "location_id": cls.zone_location2.id,
131
+ "barcode": "ZONE_SUBLOCATION_3",
132
+ }
133
+ )
134
+ )
135
+ cls.zone_sublocation4 = (
136
+ cls.env["stock.location"]
137
+ .sudo()
138
+ .create(
139
+ {
140
+ "name": "Zone sub-location 4",
141
+ "location_id": cls.zone_location2.id,
142
+ "barcode": "ZONE_SUBLOCATION_4",
143
+ }
144
+ )
145
+ )
146
+ cls.product_e = (
147
+ cls.env["product.product"]
148
+ .sudo()
149
+ .create(
150
+ {
151
+ "name": "Product E",
152
+ "type": "product",
153
+ "default_code": "E",
154
+ "barcode": "E",
155
+ "weight": 3,
156
+ }
157
+ )
158
+ )
159
+ cls.product_f = (
160
+ cls.env["product.product"]
161
+ .sudo()
162
+ .create(
163
+ {
164
+ "name": "Product F",
165
+ "type": "product",
166
+ "default_code": "F",
167
+ "barcode": "F",
168
+ "weight": 3,
169
+ }
170
+ )
171
+ )
172
+ cls.product_g = (
173
+ cls.env["product.product"]
174
+ .sudo()
175
+ .create(
176
+ {
177
+ "name": "Product G",
178
+ "type": "product",
179
+ "default_code": "G",
180
+ "barcode": "G",
181
+ "weight": 3,
182
+ }
183
+ )
184
+ )
185
+ cls.product_h = (
186
+ cls.env["product.product"]
187
+ .sudo()
188
+ .create(
189
+ {
190
+ "name": "Product H",
191
+ "type": "product",
192
+ "default_code": "H",
193
+ "barcode": "H",
194
+ "weight": 3,
195
+ }
196
+ )
197
+ )
198
+ products = (
199
+ cls.product_a
200
+ + cls.product_b
201
+ + cls.product_c
202
+ + cls.product_d
203
+ + cls.product_e
204
+ + cls.product_f
205
+ + cls.product_g
206
+ + cls.product_h
207
+ )
208
+ for product in products:
209
+ cls.env["stock.putaway.rule"].sudo().create(
210
+ {
211
+ "product_id": product.id,
212
+ "location_in_id": cls.stock_location.id,
213
+ "location_out_id": cls.shelf1.id,
214
+ }
215
+ )
216
+
217
+ cls.picking1 = picking1 = cls._create_picking(
218
+ picking_type=cls.menu1_picking_type, lines=[(cls.product_a, 10)]
219
+ )
220
+ picking1.priority = "0"
221
+ cls._fill_stock_for_moves(
222
+ picking1.move_ids, in_package=True, location=cls.zone_sublocation1
223
+ )
224
+
225
+ cls.picking2 = picking2 = cls._create_picking(
226
+ picking_type=cls.menu1_picking_type,
227
+ lines=[(cls.product_b, 10), (cls.product_c, 10)],
228
+ )
229
+ picking2.priority = "1"
230
+ cls._fill_stock_for_moves(
231
+ picking2.move_ids, in_lot=True, location=cls.zone_sublocation2
232
+ )
233
+
234
+ cls.picking3 = picking3 = cls._create_picking(
235
+ picking_type=cls.menu1_picking_type, lines=[(cls.product_d, 10)]
236
+ )
237
+ picking3.priority = "0"
238
+ cls._fill_stock_for_moves(picking3.move_ids, location=cls.zone_sublocation1)
239
+
240
+ cls.picking4 = picking4 = cls._create_picking(
241
+ picking_type=cls.menu2_picking_type, lines=[(cls.product_e, 10)]
242
+ )
243
+ cls._update_qty_in_location(cls.zone_sublocation3, cls.product_e, 6)
244
+ cls._update_qty_in_location(cls.zone_sublocation4, cls.product_e, 4)
245
+
246
+ cls.picking5 = picking5 = cls._create_picking(
247
+ picking_type=cls.menu2_picking_type,
248
+ lines=[(cls.product_b, 10), (cls.product_f, 10)],
249
+ )
250
+ cls._fill_stock_for_moves(
251
+ picking5.move_ids, in_package=True, location=cls.zone_sublocation2
252
+ )
253
+ cls.picking6 = picking6 = cls._create_picking(
254
+ picking_type=cls.menu2_picking_type,
255
+ lines=[(cls.product_g, 6), (cls.product_h, 6)],
256
+ )
257
+ cls._update_qty_in_location(cls.zone_sublocation2, cls.product_g, 6)
258
+ cls._update_qty_in_location(cls.zone_sublocation2, cls.product_h, 3)
259
+
260
+ cls.pickings = picking1 | picking2 | picking3 | picking4 | picking5 | picking6
261
+ cls.pickings.action_assign()
@@ -0,0 +1,61 @@
1
+ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+ from .test_menu_base import MenuCountersCommonCase
4
+
5
+
6
+ class TestMenuCountersCommonCase(MenuCountersCommonCase):
7
+ def test_menu_search_profile1(self):
8
+ expected_counters = {
9
+ self.menu1.id: {
10
+ "lines_count": 2,
11
+ "picking_count": 2,
12
+ "priority_lines_count": 0,
13
+ "priority_picking_count": 0,
14
+ },
15
+ self.menu2.id: {
16
+ "lines_count": 6,
17
+ "picking_count": 3,
18
+ "priority_lines_count": 0,
19
+ "priority_picking_count": 0,
20
+ },
21
+ }
22
+ expected_menu_items = (
23
+ self.env["shopfloor.menu"]
24
+ .sudo()
25
+ .search([("profile_id", "=", self.wms_profile.id)])
26
+ )
27
+ service = self.get_service("menu", profile=self.wms_profile)
28
+ response = service.dispatch("search")
29
+ self._assert_menu_response(
30
+ response,
31
+ expected_menu_items.sorted("sequence"),
32
+ expected_counters=expected_counters,
33
+ )
34
+
35
+ def test_menu_search_profile2(self):
36
+ expected_counters = {
37
+ self.menu1.id: {
38
+ "lines_count": 2,
39
+ "picking_count": 2,
40
+ "priority_lines_count": 0,
41
+ "priority_picking_count": 0,
42
+ },
43
+ self.menu2.id: {
44
+ "lines_count": 6,
45
+ "picking_count": 3,
46
+ "priority_lines_count": 0,
47
+ "priority_picking_count": 0,
48
+ },
49
+ }
50
+ expected_menu_items = (
51
+ self.env["shopfloor.menu"]
52
+ .sudo()
53
+ .search([("profile_id", "=", self.wms_profile2.id)])
54
+ )
55
+ service = self.get_service("menu", profile=self.wms_profile2)
56
+ response = service.dispatch("search")
57
+ self._assert_menu_response(
58
+ response,
59
+ expected_menu_items.sorted("sequence"),
60
+ expected_counters=expected_counters,
61
+ )