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,1121 @@
1
+ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # Copyright 2020 Akretion (http://www.akretion.com)
3
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
4
+
5
+ from odoo.tests.common import Form
6
+
7
+ from .test_single_pack_transfer_base import SinglePackTransferCommonBase
8
+
9
+ # pylint: disable=missing-return
10
+
11
+
12
+ class TestSinglePackTransfer(SinglePackTransferCommonBase):
13
+ @classmethod
14
+ def setUpClassBaseData(cls, *args, **kwargs):
15
+ super().setUpClassBaseData(*args, **kwargs)
16
+ cls.pack_a = cls.env["stock.quant.package"].create(
17
+ {"location_id": cls.stock_location.id}
18
+ )
19
+ cls.quant_a = (
20
+ cls.env["stock.quant"]
21
+ .sudo()
22
+ .create(
23
+ {
24
+ "product_id": cls.product_a.id,
25
+ "location_id": cls.shelf1.id,
26
+ "quantity": 1,
27
+ "package_id": cls.pack_a.id,
28
+ }
29
+ )
30
+ )
31
+ cls.shelf1_2 = cls.shelf1.sudo().copy({"name": "Shelf 1_2"})
32
+ cls.pack_b = cls.env["stock.quant.package"].create(
33
+ {"location_id": cls.stock_location.id}
34
+ )
35
+ cls.quant_b = (
36
+ cls.env["stock.quant"]
37
+ .sudo()
38
+ .create(
39
+ {
40
+ "product_id": cls.product_b.id,
41
+ "location_id": cls.shelf1_2.id,
42
+ "quantity": 1,
43
+ "package_id": cls.pack_b.id,
44
+ }
45
+ )
46
+ )
47
+ cls.picking = cls._create_initial_move(
48
+ lines=[(cls.product_a, 1), (cls.product_b, 1)]
49
+ )
50
+
51
+ @classmethod
52
+ def _create_initial_move(cls, lines):
53
+ """Create the move to satisfy the pre-condition before /start"""
54
+ picking_form = Form(cls.env["stock.picking"])
55
+ picking_form.picking_type_id = cls.picking_type
56
+ picking_form.location_id = cls.stock_location
57
+ picking_form.location_dest_id = cls.shelf2
58
+ for line in lines:
59
+ with picking_form.move_ids_without_package.new() as move:
60
+ move.product_id = line[0]
61
+ move.product_uom_qty = line[1]
62
+ picking = picking_form.save()
63
+ picking.action_confirm()
64
+ picking.action_assign()
65
+ return picking
66
+
67
+ def _simulate_started(self, package):
68
+ """Replicate what the /start endpoint would do on the given package.
69
+
70
+ Used to test the next endpoints (/validate and /cancel)
71
+ """
72
+ package_level = self.picking.move_line_ids.package_level_id.filtered(
73
+ lambda pl: pl.package_id == package
74
+ )
75
+ package_level.is_done = True
76
+ return package_level
77
+
78
+ def _response_package_level_data(self, package_level):
79
+ return {
80
+ "id": package_level.id,
81
+ "name": package_level.package_id.name,
82
+ "location_src": self.data.location(package_level.location_id),
83
+ "location_dest": self.data.location(package_level.location_dest_id),
84
+ "picking": self.data.picking(self.picking),
85
+ "products": self.data.products(self.product_a),
86
+ }
87
+
88
+ def test_start(self):
89
+ """Test the happy path for single pack transfer /start endpoint
90
+
91
+ We scan the barcode of the pack (simplest use case).
92
+
93
+ The pre-conditions:
94
+
95
+ * A Pack exists in Stock/Shelf1.
96
+ * A stock picking exists to move the Pack from Stock/Shelf1 to
97
+ Stock/Shelf2. The move is "assigned".
98
+
99
+ Expected result:
100
+
101
+ * The package level of the move is set to "is_done".
102
+
103
+ The next step in the workflow is to call /validate with the
104
+ package level that will set the move and picking to done.
105
+ """
106
+ barcode = self.pack_a.name
107
+ params = {"barcode": barcode}
108
+
109
+ package_level = self.picking.move_line_ids.package_level_id.filtered(
110
+ lambda pl: pl.package_id == self.pack_a
111
+ )
112
+ self.assertFalse(package_level.is_done)
113
+
114
+ # Simulate the client scanning a package's barcode, which
115
+ # in turns should start the operation in odoo
116
+ response = self.service.dispatch("start", params=params)
117
+
118
+ self.assertTrue(package_level.is_done)
119
+ self.assert_response(
120
+ response,
121
+ next_state="scan_location",
122
+ data=dict(
123
+ self._response_package_level_data(package_level),
124
+ confirmation_required=False,
125
+ ),
126
+ )
127
+
128
+ def test_start_no_operation(self):
129
+ """Test /start when there is no operation to move the pack
130
+
131
+ The pre-conditions:
132
+
133
+ * A Pack exists in Stock/Shelf1.
134
+ * No stock picking exists to move the Pack from Stock/Shelf1 to
135
+ Stock/Shelf2, or the state is not assigned.
136
+
137
+ Expected result:
138
+
139
+ * Return a message
140
+ """
141
+ barcode = self.pack_a.name
142
+ params = {"barcode": barcode}
143
+ self.picking.do_unreserve()
144
+
145
+ # Simulate the client scanning a package's barcode, which
146
+ # in turns should start the operation in odoo
147
+ response = self.service.dispatch("start", params=params)
148
+
149
+ self.assert_response(
150
+ response,
151
+ next_state="start",
152
+ message={
153
+ "message_type": "error",
154
+ "body": "No pending operation for package {}.".format(self.pack_a.name),
155
+ },
156
+ )
157
+
158
+ def test_start_no_operation_create(self):
159
+ """Test /start when there is no operation to move the pack, it is created
160
+
161
+ The pre-conditions:
162
+
163
+ * The option "Allow Move Creation" is turned on on the menu
164
+ * A Pack exists in Stock/Shelf1.
165
+ * No stock picking exists to move the Pack from Stock/Shelf1 to
166
+ Stock/Shelf2, or the state is not assigned.
167
+
168
+ Expected result:
169
+
170
+ * Create a stock.picking, move, package level and continue with the
171
+ workflow
172
+ """
173
+ self.menu.sudo().allow_move_create = True
174
+ barcode = self.pack_a.name
175
+ params = {"barcode": barcode}
176
+ self.picking.do_unreserve()
177
+
178
+ # Simulate the client scanning a package's barcode, which
179
+ # in turns should start the operation in odoo
180
+ response = self.service.dispatch("start", params=params)
181
+
182
+ move_line = self.env["stock.move.line"].search(
183
+ [("package_id", "=", self.pack_a.id)]
184
+ )
185
+ package_level = move_line.package_level_id
186
+
187
+ self.assertTrue(package_level.is_done)
188
+
189
+ expected_data = {
190
+ "id": package_level.id,
191
+ "name": package_level.package_id.name,
192
+ "location_src": self.data.location(self.shelf1),
193
+ "location_dest": self.data.location(
194
+ self.picking_type.default_location_dest_id
195
+ ),
196
+ "picking": self.data.picking(package_level.picking_id),
197
+ "products": self.data.products(self.product_a),
198
+ "confirmation_required": False,
199
+ }
200
+
201
+ self.assert_response(response, next_state="scan_location", data=expected_data)
202
+
203
+ def test_start_barcode_not_known(self):
204
+ """Test /start when the barcode is unknown
205
+
206
+ The pre-conditions:
207
+
208
+ * Nothing
209
+
210
+ Expected result:
211
+
212
+ * Return a message
213
+ """
214
+ params = {"barcode": "THIS_BARCODE_DOES_NOT_EXIST"}
215
+ response = self.service.dispatch("start", params=params)
216
+ self.assert_response(
217
+ response,
218
+ next_state="start",
219
+ message={
220
+ "message_type": "error",
221
+ "body": "The package THIS_BARCODE_DOES_NOT_EXIST" " doesn't exist",
222
+ },
223
+ )
224
+
225
+ def test_start_pack_empty(self):
226
+ """Test /start when the scanned pack is empty
227
+
228
+ The pre-conditions:
229
+
230
+ * Nothing
231
+
232
+ Expected result:
233
+
234
+ * Return a message
235
+ """
236
+ pack_empty = self.env["stock.quant.package"].create({})
237
+ pack_code = pack_empty.name
238
+ params = {"barcode": pack_code}
239
+ response = self.service.dispatch("start", params=params)
240
+ self.assert_response(
241
+ response,
242
+ next_state="start",
243
+ message=self.service.msg_store.package_has_no_product_to_take(pack_code),
244
+ )
245
+
246
+ def test_start_pack_from_location(self):
247
+ """Test /start, finding the pack from location's barcode
248
+
249
+ When we scan a location which contains only one pack,
250
+ we want to move this pack.
251
+
252
+ The pre-conditions:
253
+
254
+ * A Pack exists in Stock/Shelf1.
255
+ * A stock picking exists to move the Pack from Stock/Shelf1 to
256
+ Stock/Shelf2. The move is "assigned".
257
+
258
+ Expected result:
259
+
260
+ * The package level of the move is set to "is_done".
261
+
262
+ The next step in the workflow is to call /validate with the
263
+ package level that will set the move and picking to done.
264
+ """
265
+ barcode = self.shelf1.barcode
266
+ params = {"barcode": barcode}
267
+ response = self.service.dispatch("start", params=params)
268
+ self.assert_response(
269
+ # We only care about the fact that we jump to the next
270
+ # screen, so it found the pack. The details are already
271
+ # checked in the test_start test.
272
+ response,
273
+ next_state="scan_location",
274
+ data=self.ANY,
275
+ )
276
+
277
+ def test_start_pack_from_location_empty(self):
278
+ """Test /start, scan location's barcode without pack
279
+
280
+ When we scan a location which contains no packs,
281
+ we ask the user to scan a pack.
282
+
283
+ The pre-conditions:
284
+
285
+ * No packs exists in Stock/Shelf2
286
+
287
+ Expected result:
288
+
289
+ * Return a message
290
+ """
291
+ barcode = self.shelf2.barcode
292
+ params = {"barcode": barcode}
293
+ response = self.service.dispatch("start", params=params)
294
+ self.assert_response(
295
+ response,
296
+ next_state="start",
297
+ message={
298
+ "message_type": "error",
299
+ "body": "Location %s doesn't contain any package."
300
+ % (self.shelf2.name,),
301
+ },
302
+ )
303
+
304
+ def test_start_pack_from_location_several_packs(self):
305
+ """Test /start, scan location's barcode with several packs
306
+
307
+ When we scan a location which contains several packs,
308
+ we ask the user to scan a pack.
309
+
310
+ The pre-conditions:
311
+
312
+ * 2 packs exists in Stock/Shelf1.
313
+
314
+ Expected result:
315
+
316
+ * Return a message
317
+ """
318
+ pack_b = self.env["stock.quant.package"].create(
319
+ {"location_id": self.stock_location.id}
320
+ )
321
+ self.env["stock.quant"].sudo().create(
322
+ {
323
+ "product_id": self.product_a.id,
324
+ "location_id": self.shelf1.id,
325
+ "quantity": 1,
326
+ "package_id": pack_b.id,
327
+ }
328
+ )
329
+ barcode = self.shelf1.barcode
330
+ params = {"barcode": barcode}
331
+ response = self.service.dispatch("start", params=params)
332
+ self.assert_response(
333
+ response,
334
+ next_state="start",
335
+ message={
336
+ "message_type": "warning",
337
+ "body": "Several packages found in %s, please scan a package."
338
+ % (self.shelf1.name,),
339
+ },
340
+ )
341
+
342
+ def test_start_pack_outside_of_location(self):
343
+ """Test /start, scan a pack outside of the picking type location
344
+
345
+ The pre-conditions:
346
+
347
+ * A pack exists in a location outside of Stock (the default source
348
+ location of the picking type associated with the process)
349
+
350
+ Expected result:
351
+
352
+ * Return a message
353
+ """
354
+ self.pack_a.location_id = self.dispatch_location
355
+ barcode = self.pack_a.name
356
+ params = {"barcode": barcode}
357
+ response = self.service.dispatch("start", params=params)
358
+ self.assert_response(
359
+ response,
360
+ next_state="start",
361
+ message={
362
+ "message_type": "error",
363
+ "body": "You cannot work on a package (%s) outside of locations: %s"
364
+ % (self.pack_a.name, self.picking_type.default_location_src_id.name),
365
+ },
366
+ )
367
+
368
+ def test_start_already_started(self):
369
+ """Test /start when it was already started
370
+
371
+ We scan the barcode of the pack (simplest use case).
372
+
373
+ The pre-conditions:
374
+
375
+ * A Pack exists in Stock/Shelf1.
376
+ * A stock picking exists to move the Pack from Stock/Shelf1 to
377
+ Stock/Shelf2. The move is "assigned".
378
+ * Start is already called once
379
+
380
+ Expected result:
381
+
382
+ * Transition for confirmation with such message
383
+
384
+ The next step in the workflow is to call /validate with the
385
+ package level that will set the move and picking to done.
386
+ """
387
+ barcode = self.pack_a.name
388
+ params = {"barcode": barcode}
389
+
390
+ package_level = self._simulate_started(self.pack_a)
391
+ self.assertTrue(package_level.is_done)
392
+
393
+ # Simulate the client scanning a package's barcode, which
394
+ # in turns should start the operation in odoo
395
+ response = self.service.dispatch("start", params=params)
396
+
397
+ self.assert_response(
398
+ response,
399
+ next_state="start",
400
+ message={
401
+ "message_type": "warning",
402
+ "body": "Operation's already running."
403
+ " Would you like to take it over?",
404
+ },
405
+ data=dict(
406
+ self._response_package_level_data(package_level),
407
+ confirmation_required=True,
408
+ ),
409
+ )
410
+
411
+ def test_validate(self):
412
+ """Test the happy path for single pack transfer /validate endpoint
413
+
414
+ The pre-conditions:
415
+
416
+ * /start has been called
417
+ * "completion info" is not active on the picking type
418
+
419
+ Expected result:
420
+
421
+ * The move associated to the package level is 'done'
422
+ """
423
+ # setup the picking as we need, like if the move line
424
+ # was already started by the first step (start operation)
425
+ package_level = self._simulate_started(self.pack_a)
426
+
427
+ # now, call the service to proceed with validation of the
428
+ # movement
429
+ response = self.service.dispatch(
430
+ "validate",
431
+ params={
432
+ "package_level_id": package_level.id,
433
+ "location_barcode": self.shelf2.barcode,
434
+ },
435
+ )
436
+
437
+ self.assert_response(
438
+ response,
439
+ next_state="start",
440
+ message={
441
+ "message_type": "success",
442
+ "body": "The pack has been moved, you can scan a new pack.",
443
+ },
444
+ )
445
+
446
+ self.assertRecordValues(
447
+ package_level.move_line_ids,
448
+ [{"qty_done": 1.0, "location_dest_id": self.shelf2.id, "state": "done"}],
449
+ )
450
+ self.assertRecordValues(
451
+ package_level.move_line_ids.move_id,
452
+ [{"location_dest_id": self.shelf2.id, "state": "done"}],
453
+ )
454
+
455
+ def test_validate_completion_info(self):
456
+ """Test /validate when the picking is the last (show completion info)
457
+
458
+ When the picking is the last, we display an information screen on the
459
+ js application.
460
+
461
+ The pre-conditions:
462
+
463
+ * /start has been called
464
+ * "completion info" is active on the picking type
465
+ * the picking must be the last (it must not have destination moves with
466
+ unprocessed origin moves)
467
+
468
+ Expected result:
469
+
470
+ * The move associated to the package level is 'done'
471
+ * The transition goes to the completion info screen instead of starting
472
+ over
473
+ """
474
+ # activate the computation of this field, so we have a chance to
475
+ # transition to the 'show completion info' screen.
476
+ self.picking_type.sudo().display_completion_info = True
477
+
478
+ # create a chained picking after the current one
479
+ next_picking = self.picking.copy(
480
+ {
481
+ "picking_type_id": self.wh.out_type_id.id,
482
+ "location_id": self.picking.location_dest_id.id,
483
+ "location_dest_id": self.customer_location.id,
484
+ }
485
+ )
486
+ next_picking.move_ids.write(
487
+ {"move_orig_ids": [(6, 0, self.picking.move_ids.ids)]}
488
+ )
489
+ next_picking.action_confirm()
490
+
491
+ # process the first package
492
+ package_level_a = self._simulate_started(self.pack_a)
493
+ # validate the first package
494
+ response = self.service.dispatch(
495
+ "validate",
496
+ params={
497
+ "package_level_id": package_level_a.id,
498
+ "location_barcode": self.shelf2.barcode,
499
+ },
500
+ )
501
+ self.assertEqual(package_level_a.picking_id.state, "done")
502
+ # check the response: still no completion info message as we still have
503
+ # the second package to process
504
+ self.assert_response(
505
+ response,
506
+ next_state="start",
507
+ message={
508
+ "message_type": "success",
509
+ "body": "The pack has been moved, you can scan a new pack.",
510
+ },
511
+ )
512
+ # process the second package
513
+ package_level_b = self._simulate_started(self.pack_b)
514
+ # validate the second package
515
+ response = self.service.dispatch(
516
+ "validate",
517
+ params={
518
+ "package_level_id": package_level_b.id,
519
+ "location_barcode": self.shelf2.barcode,
520
+ },
521
+ )
522
+ self.assertEqual(package_level_b.picking_id.state, "done")
523
+ self.assertNotEqual(package_level_a.picking_id, package_level_b.picking_id)
524
+ # check the response: the chained transfer is ready to be processed now
525
+ # that all the packages have been processed
526
+ self.assert_response(
527
+ response,
528
+ next_state="start",
529
+ popup={
530
+ "body": "Last operation of transfer {}. Next operation "
531
+ "({}) is ready to proceed.".format(self.picking.name, next_picking.name)
532
+ },
533
+ message={
534
+ "message_type": "success",
535
+ "body": "The pack has been moved, you can scan a new pack.",
536
+ },
537
+ )
538
+
539
+ def test_validate_not_found(self):
540
+ """Test a call on /validate on package level not found
541
+
542
+ Expected result:
543
+
544
+ * No change in odoo, Transition with a message
545
+ """
546
+ response = self.service.dispatch(
547
+ "validate",
548
+ params={"package_level_id": -1, "location_barcode": self.shelf1.barcode},
549
+ )
550
+
551
+ self.assert_response(
552
+ response,
553
+ next_state="start",
554
+ message={
555
+ "message_type": "error",
556
+ "body": "This operation does not exist anymore.",
557
+ },
558
+ )
559
+
560
+ def test_validate_location_not_found(self):
561
+ """Test a call on /validate on location not found
562
+
563
+ The pre-conditions:
564
+
565
+ * /start has been called
566
+
567
+ Expected result:
568
+
569
+ * No change in odoo, Transition with a message
570
+ """
571
+ # setup the picking as we need, like if the move line
572
+ # was already started by the first step (start operation)
573
+ package_level = self._simulate_started(self.pack_a)
574
+
575
+ response = self.service.dispatch(
576
+ "validate",
577
+ params={
578
+ "package_level_id": package_level.id,
579
+ "location_barcode": "THIS_BARCODE_DOES_NOT_EXISTS",
580
+ },
581
+ )
582
+
583
+ self.assert_response(
584
+ response,
585
+ next_state="scan_location",
586
+ data=self.ANY,
587
+ message={
588
+ "message_type": "error",
589
+ "body": "No location found for this barcode.",
590
+ },
591
+ )
592
+
593
+ def test_validate_location_forbidden(self):
594
+ """Test a call on /validate on a forbidden location (not child of
595
+ picking or move)
596
+
597
+ The pre-conditions:
598
+
599
+ * /start has been called
600
+
601
+ Expected result:
602
+
603
+ * No change in odoo, Transition with a message
604
+
605
+ Note: the location is forbidden when a location is not a child
606
+ of the destination location of the picking used for the process
607
+ """
608
+ # setup the picking as we need, like if the move line
609
+ # was already started by the first step (start operation)
610
+ package_level = self._simulate_started(self.pack_a)
611
+
612
+ response = self.service.dispatch(
613
+ "validate",
614
+ params={
615
+ "package_level_id": package_level.id,
616
+ # this location is outside of the expected destination
617
+ "location_barcode": self.dispatch_location.barcode,
618
+ },
619
+ )
620
+
621
+ self.assert_response(
622
+ response,
623
+ next_state="scan_location",
624
+ data=self.ANY,
625
+ message={"message_type": "error", "body": "You cannot place it here"},
626
+ )
627
+
628
+ def test_validate_location_move_not_child_of_picking_allowed(self):
629
+ """Test a call on /validate on a location not child of picking but child of move
630
+
631
+ The pre-conditions:
632
+
633
+ * /start has been called
634
+
635
+ Expected result:
636
+
637
+ * No change in odoo, Transition with a message
638
+
639
+ Note: the location is allowed when the move location has changed and
640
+ that location is a child of the destination location of the move
641
+ """
642
+ # setup the picking as we need, like if the move line
643
+ # was already started by the first step (start operation)
644
+ package_level = self._simulate_started(self.pack_a)
645
+
646
+ location = package_level.location_dest_id.location_id
647
+ package_level.location_dest_id = location
648
+ package_level.move_line_ids.move_id.location_dest_id = location
649
+
650
+ response = self.service.dispatch(
651
+ "validate",
652
+ params={
653
+ "package_level_id": package_level.id,
654
+ "location_barcode": location.barcode,
655
+ },
656
+ )
657
+
658
+ self.assert_response(
659
+ response,
660
+ next_state="start",
661
+ message={
662
+ "message_type": "success",
663
+ "body": "The pack has been moved, you can scan a new pack.",
664
+ },
665
+ )
666
+
667
+ def test_validate_location_to_confirm(self):
668
+ """Test a call on /validate on a location to confirm
669
+
670
+ The pre-conditions:
671
+
672
+ * /start has been called
673
+
674
+ Expected result:
675
+
676
+ * No change in odoo, transition with a message
677
+
678
+ Note: a location to confirm is when a location is a child
679
+ of the destination location of the picking type used for the process
680
+ but not a child or the expected destination
681
+ """
682
+ # setup the picking as we need, like if the move line
683
+ # was already started by the first step (start operation)
684
+ package_level = self._simulate_started(self.pack_a)
685
+
686
+ sub_shelf1 = (
687
+ self.env["stock.location"]
688
+ .sudo()
689
+ .create(
690
+ {
691
+ "name": "subshelf1",
692
+ "barcode": "subshelf1",
693
+ "location_id": self.shelf2.id,
694
+ }
695
+ )
696
+ )
697
+ sub_shelf2 = (
698
+ self.env["stock.location"]
699
+ .sudo()
700
+ .create(
701
+ {
702
+ "name": "subshelf2",
703
+ "barcode": "subshelf2",
704
+ "location_id": self.shelf2.id,
705
+ }
706
+ )
707
+ )
708
+
709
+ # expected destination is 'shelf2', we'll scan shelf1 which must
710
+ # ask a confirmation to the user (it's still in the same picking type)
711
+ package_level.location_dest_id = sub_shelf1
712
+ response = self.service.dispatch(
713
+ "validate",
714
+ params={
715
+ "package_level_id": package_level.id,
716
+ "location_barcode": sub_shelf2.barcode,
717
+ },
718
+ )
719
+
720
+ message = self.service._actions_for("message").confirm_location_changed(
721
+ sub_shelf1, sub_shelf2
722
+ )
723
+ self.assert_response(
724
+ response,
725
+ next_state="scan_location",
726
+ message=message,
727
+ data=dict(
728
+ self._response_package_level_data(package_level),
729
+ confirmation_required=True,
730
+ ),
731
+ )
732
+
733
+ def test_validate_location_with_confirm(self):
734
+ """Test a call on /validate on a different location with confirmation
735
+
736
+ The pre-conditions:
737
+
738
+ * /start has been called
739
+
740
+ Expected result:
741
+
742
+ * Ignore the fact that the scanned location is not the expected
743
+ * Change the destination of the move line to the scanned one
744
+ * The move associated to the package level is 'done'
745
+
746
+ Note: a location to confirm is when a location is a child
747
+ of the destination location of the picking type used for the process
748
+ but not a child or the expected destination.
749
+ In such situation, the js application has to call /validate with
750
+ a ``confirmation`` flag.
751
+ """
752
+ # setup the picking as we need, like if the move line
753
+ # was already started by the first step (start operation)
754
+ package_level = self._simulate_started(self.pack_a)
755
+
756
+ # expected destination is 'shelf1', we'll scan shelf2 which must
757
+ # ask a confirmation to the user (it's still in the same picking type)
758
+ response = self.service.dispatch(
759
+ "validate",
760
+ params={
761
+ "package_level_id": package_level.id,
762
+ "location_barcode": self.shelf2.barcode,
763
+ # acknowledge the change of destination
764
+ "confirmation": True,
765
+ },
766
+ )
767
+
768
+ self.assert_response(
769
+ response,
770
+ next_state="start",
771
+ message={
772
+ "message_type": "success",
773
+ "body": "The pack has been moved, you can scan a new pack.",
774
+ },
775
+ )
776
+
777
+ self.assertRecordValues(
778
+ package_level.move_line_ids,
779
+ [{"qty_done": 1.0, "location_dest_id": self.shelf2.id, "state": "done"}],
780
+ )
781
+ self.assertRecordValues(
782
+ package_level.move_line_ids.move_id,
783
+ [{"location_dest_id": self.shelf2.id, "state": "done"}],
784
+ )
785
+
786
+ def test_cancel(self):
787
+ """Test the happy path for single pack transfer /cancel endpoint
788
+
789
+ The pre-conditions:
790
+
791
+ * /start has been called
792
+
793
+ Expected result:
794
+
795
+ * The package level has is_done to False
796
+ """
797
+ # setup the picking as we need, like if the move line
798
+ # was already started by the first step (start operation)
799
+ package_level = self._simulate_started(self.pack_a)
800
+ self.assertTrue(package_level.is_done)
801
+
802
+ # keep references for later checks
803
+ move = package_level.move_line_ids.move_id
804
+ picking = move.picking_id
805
+
806
+ # now, call the service to cancel
807
+ response = self.service.dispatch(
808
+ "cancel", params={"package_level_id": package_level.id}
809
+ )
810
+ self.assertRecordValues(move, [{"state": "assigned"}])
811
+ self.assertRecordValues(picking, [{"state": "assigned"}])
812
+ self.assertRecordValues(package_level, [{"is_done": False}])
813
+
814
+ self.assert_response(
815
+ response,
816
+ next_state="start",
817
+ message={
818
+ "message_type": "success",
819
+ "body": "Canceled, you can scan a new pack.",
820
+ },
821
+ )
822
+
823
+ def test_cancel_already_canceled(self):
824
+ """Test a call on /cancel for already canceled package level
825
+
826
+ The pre-conditions:
827
+
828
+ * /start has been called
829
+ * /cancel has been called elsewhere or 'is_done' removed in odoo
830
+
831
+ Expected result:
832
+
833
+ * Nothing happens, transition with a message
834
+ """
835
+ # setup the picking as we need, like if the move line
836
+ # was already started by the first step (start operation)
837
+ package_level_a = self._simulate_started(self.pack_a)
838
+ # keep references for later checks
839
+ move_a = package_level_a.move_line_ids.move_id
840
+ move_lines_a = package_level_a.move_line_ids
841
+ picking = move_a.picking_id
842
+ # someone cancel the work started by our operator
843
+ move_lines_a.write({"qty_done": 0})
844
+ move_a._action_cancel()
845
+
846
+ # now, call the service to cancel the first package
847
+ response = self.service.dispatch(
848
+ "cancel", params={"package_level_id": package_level_a.id}
849
+ )
850
+ self.assertRecordValues(move_a, [{"state": "cancel"}])
851
+ self.assertRecordValues(picking, [{"state": "assigned"}])
852
+ self.assertFalse(package_level_a.move_line_ids)
853
+ self.assertFalse(move_lines_a.exists())
854
+
855
+ self.assert_response(
856
+ response,
857
+ next_state="start",
858
+ message={
859
+ "message_type": "error",
860
+ "body": "This operation does not exist anymore.",
861
+ },
862
+ )
863
+ package_level_b = self._simulate_started(self.pack_b)
864
+ # keep references for later checks
865
+ move_b = package_level_b.move_line_ids.move_id
866
+ # someone cancel the work started by our operator
867
+ move_lines_b = package_level_b.move_line_ids
868
+ move_lines_b.write({"qty_done": 0})
869
+ move_b._action_cancel()
870
+ # then cancel the second package
871
+ response = self.service.dispatch(
872
+ "cancel", params={"package_level_id": package_level_b.id}
873
+ )
874
+ self.assertRecordValues(move_b, [{"state": "cancel"}])
875
+ picking.invalidate_recordset(["state"])
876
+ self.assertRecordValues(picking, [{"state": "cancel"}])
877
+
878
+ def test_cancel_already_done(self):
879
+ """Test a call on /cancel on move already done
880
+
881
+ The pre-conditions:
882
+
883
+ * /start has been called
884
+ * /validate has been called or move set to done in odoo
885
+
886
+ Expected result:
887
+
888
+ * No change in odoo, Transition with a message
889
+ """
890
+ # setup the picking as we need, like if the move line
891
+ # was already started by the first step (start operation)
892
+ package_levels = self._simulate_started(self.pack_a) | self._simulate_started(
893
+ self.pack_b
894
+ )
895
+
896
+ # keep references for later checks
897
+ moves = package_levels.move_line_ids.move_id
898
+ picking = moves.picking_id
899
+
900
+ # someone cancel the work started by our operator
901
+ moves.extract_and_action_done()
902
+
903
+ # now, call the service to cancel
904
+ response = self.service.dispatch(
905
+ "cancel", params={"package_level_id": package_levels[0].id}
906
+ )
907
+ response = self.service.dispatch(
908
+ "cancel", params={"package_level_id": package_levels[1].id}
909
+ )
910
+ self.assertRecordValues(moves, [{"state": "done"}, {"state": "done"}])
911
+ self.assertRecordValues(picking, [{"state": "done"}])
912
+
913
+ self.assert_response(
914
+ response,
915
+ next_state="start",
916
+ message={"message_type": "info", "body": "Operation already processed."},
917
+ )
918
+
919
+ def test_cancel_not_found(self):
920
+ """Test a call on /cancel on package level not found
921
+
922
+ Expected result:
923
+
924
+ * No change in odoo, Transition with a message
925
+ """
926
+ response = self.service.dispatch("cancel", params={"package_level_id": -1})
927
+ self.assert_response(
928
+ response,
929
+ next_state="start",
930
+ message={
931
+ "message_type": "error",
932
+ "body": "This operation does not exist anymore.",
933
+ },
934
+ )
935
+
936
+
937
+ class SinglePackTransferSpecialCase(SinglePackTransferCommonBase):
938
+ def test_start_package_unreserve_ok(self):
939
+ """Test /start when the package was already reserved...
940
+
941
+ ...for another picking type and unreserving is allowed.
942
+
943
+ When we scan a location which contains only one package,
944
+ we want to move this package.
945
+
946
+ The pre-conditions:
947
+
948
+ * A package exists in Stock/Shelf1.
949
+ * A stock picking exists to move the package from Stock/Shelf1 to
950
+ Out with a different picking type. The move is "assigned".
951
+ * Another package exists in Stock
952
+
953
+ Expected result:
954
+
955
+ * the original transfer is reserved to move the other package from Stock
956
+ * a new transfer is created to move the package from Shelf1
957
+
958
+ The next step in the workflow is to call /validate with the
959
+ package level that will set the move and picking to done.
960
+ """
961
+ self.menu.sudo().allow_move_create = True
962
+ self.menu.sudo().allow_unreserve_other_moves = True
963
+
964
+ package = self.env["stock.quant.package"].create({})
965
+ self._update_qty_in_location(self.shelf1, self.product_a, 10, package=package)
966
+ # create a picking of another picking type
967
+ picking = self._create_picking(
968
+ picking_type=self.wh.out_type_id, lines=[(self.product_a, 10)]
969
+ )
970
+ picking.action_assign()
971
+
972
+ # create another package that should be used when the picking will
973
+ # get re-assigned
974
+ package2 = self.env["stock.quant.package"].create({})
975
+ self._update_qty_in_location(
976
+ self.stock_location, self.product_a, 10, package=package2
977
+ )
978
+
979
+ barcode = self.shelf1.barcode
980
+ params = {"barcode": barcode}
981
+ response = self.service.dispatch("start", params=params)
982
+
983
+ new_picking = self.env["stock.picking"].search(
984
+ [("picking_type_id", "=", self.picking_type.id)]
985
+ )
986
+ self.assertEqual(len(new_picking), 1)
987
+ new_package_level = new_picking.package_level_ids
988
+
989
+ self.assert_response(
990
+ # We only care about the fact that we jump to the next
991
+ # screen, so it found the pack. The details are already
992
+ # checked in the test_start test.
993
+ response,
994
+ next_state="scan_location",
995
+ data=dict(
996
+ self.service._data_after_package_scanned(new_package_level),
997
+ confirmation_required=False,
998
+ ),
999
+ )
1000
+ self.assertRecordValues(
1001
+ picking.package_level_ids, [{"package_id": package2.id}]
1002
+ )
1003
+
1004
+ self.assertRecordValues(new_package_level, [{"package_id": package.id}])
1005
+
1006
+ def test_start_package_unreserve_picked_error(self):
1007
+ """Test /start when the package was already reserved...
1008
+
1009
+ ...for another picking type and the other move is already picked.
1010
+
1011
+ When we scan a location which contains only one package,
1012
+ we want to move this package.
1013
+
1014
+ The pre-conditions:
1015
+
1016
+ * A package exists in Stock/Shelf1.
1017
+ * A stock picking exists to move the package from Stock/Shelf1 to
1018
+ Out with a different picking type. The move is "assigned".
1019
+
1020
+ Expected result:
1021
+
1022
+ * receive an error that we cannot pick the package
1023
+ """
1024
+ self.menu.sudo().allow_move_create = True
1025
+ self.menu.sudo().allow_unreserve_other_moves = True
1026
+
1027
+ package = self.env["stock.quant.package"].create({})
1028
+ self._update_qty_in_location(self.shelf1, self.product_a, 10, package=package)
1029
+ # create a picking of another picking type
1030
+ picking = self._create_picking(
1031
+ picking_type=self.wh.out_type_id, lines=[(self.product_a, 10)]
1032
+ )
1033
+ picking.action_assign()
1034
+
1035
+ # pick the goods
1036
+ picking.move_line_ids.qty_done = 10
1037
+
1038
+ barcode = self.shelf1.barcode
1039
+ params = {"barcode": barcode}
1040
+ response = self.service.dispatch("start", params=params)
1041
+ self.assert_response(
1042
+ response,
1043
+ next_state="start",
1044
+ message=self.service.msg_store.package_already_picked_by(package, picking),
1045
+ )
1046
+ # no change in the picking
1047
+ self.assertEqual(picking.state, "assigned")
1048
+ self.assertRecordValues(picking.package_level_ids, [{"package_id": package.id}])
1049
+
1050
+ def test_start_package_unreserve_disabled_error(self):
1051
+ """Test /start when the package was already reserved...
1052
+
1053
+ ...for another picking type and unreserving is disallowed.
1054
+
1055
+ When we scan a location which contains only one package,
1056
+ we want to move this package.
1057
+
1058
+ The pre-conditions:
1059
+
1060
+ * A package exists in Stock/Shelf1.
1061
+ * A stock picking exists to move the package from Stock/Shelf1 to
1062
+ Out with a different picking type. The move is "assigned".
1063
+
1064
+ Expected result:
1065
+
1066
+ * receive an error that we cannot pick the package
1067
+ """
1068
+ self.menu.sudo().allow_move_create = True
1069
+ self.menu.sudo().allow_unreserve_other_moves = False
1070
+
1071
+ package = self.env["stock.quant.package"].create({})
1072
+ self._update_qty_in_location(self.shelf1, self.product_a, 10, package=package)
1073
+ # create a picking of another picking type
1074
+ picking = self._create_picking(
1075
+ picking_type=self.wh.out_type_id, lines=[(self.product_a, 10)]
1076
+ )
1077
+ picking.action_assign()
1078
+ barcode = self.shelf1.barcode
1079
+ params = {"barcode": barcode}
1080
+ response = self.service.dispatch("start", params=params)
1081
+ self.assert_response(
1082
+ response,
1083
+ next_state="start",
1084
+ message=self.service.msg_store.package_already_picked_by(package, picking),
1085
+ )
1086
+ # no change in the picking
1087
+ self.assertEqual(picking.state, "assigned")
1088
+ self.assertRecordValues(picking.package_level_ids, [{"package_id": package.id}])
1089
+
1090
+ def test_start_package_unreserve_no_create_error(self):
1091
+ """Test /start when the package was already reserved...
1092
+
1093
+ ...for another picking type and unreserving is allowed
1094
+ and the option to create a move is not allowed.
1095
+
1096
+ This test ensure that the unreservation of the first package
1097
+ is rollbacked.
1098
+
1099
+ """
1100
+ self.menu.sudo().allow_move_create = False
1101
+ self.menu.sudo().allow_unreserve_other_moves = True
1102
+
1103
+ package = self.env["stock.quant.package"].create({})
1104
+ self._update_qty_in_location(self.shelf1, self.product_a, 10, package=package)
1105
+ # create a picking of another picking type
1106
+ picking = self._create_picking(
1107
+ picking_type=self.wh.out_type_id, lines=[(self.product_a, 10)]
1108
+ )
1109
+ picking.action_assign()
1110
+ self.assertEqual(picking.state, "assigned")
1111
+ barcode = self.shelf1.barcode
1112
+ params = {"barcode": barcode}
1113
+ response = self.service.dispatch("start", params=params)
1114
+ self.assert_response(
1115
+ response,
1116
+ next_state="start",
1117
+ message=self.service.msg_store.no_pending_operation_for_pack(package),
1118
+ )
1119
+ # no change in the picking
1120
+ self.assertEqual(picking.state, "assigned")
1121
+ self.assertRecordValues(picking.package_level_ids, [{"package_id": package.id}])