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,911 @@
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 ClusterPickingUnloadingCommonCase(ClusterPickingCommonCase):
10
+ @classmethod
11
+ def setUpClassBaseData(cls, *args, **kwargs):
12
+ super().setUpClassBaseData(*args, **kwargs)
13
+
14
+ # activate the computation of this field, so we have a chance to
15
+ # transition to the 'show completion info' popup.
16
+ cls.picking_type.sudo().display_completion_info = True
17
+
18
+ cls.batch = cls._create_picking_batch(
19
+ [
20
+ [
21
+ cls.BatchProduct(product=cls.product_a, quantity=10),
22
+ cls.BatchProduct(product=cls.product_b, quantity=10),
23
+ ],
24
+ [cls.BatchProduct(product=cls.product_a, quantity=10)],
25
+ ]
26
+ )
27
+ cls._simulate_batch_selected(cls.batch)
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
+ two_lines_product_a = cls.two_lines_picking.move_line_ids.filtered(
36
+ lambda line: line.product_id == cls.product_a
37
+ )
38
+ two_lines_product_b = cls.two_lines_picking.move_line_ids - two_lines_product_a
39
+ # force order of move lines to use in tests
40
+ cls.move_lines = (
41
+ cls.one_line_picking.move_line_ids
42
+ + two_lines_product_a
43
+ + two_lines_product_b
44
+ )
45
+
46
+ cls.bin1 = cls.env["stock.quant.package"].create({})
47
+ cls.bin2 = cls.env["stock.quant.package"].create({})
48
+ cls.packing_a_location = (
49
+ cls.env["stock.location"]
50
+ .sudo()
51
+ .create(
52
+ {
53
+ "name": "Packing A",
54
+ "barcode": "Packing-A",
55
+ "location_id": cls.packing_location.id,
56
+ }
57
+ )
58
+ )
59
+ cls.packing_b_location = (
60
+ cls.env["stock.location"]
61
+ .sudo()
62
+ .create(
63
+ {
64
+ "name": "Packing B",
65
+ "barcode": "Packing-B",
66
+ "location_id": cls.packing_location.id,
67
+ }
68
+ )
69
+ )
70
+
71
+
72
+ class ClusterPickingPrepareUnloadCase(ClusterPickingUnloadingCommonCase):
73
+ """Tests covering the /prepare_unload endpoint
74
+
75
+ Destination packages have been set on all the move lines of the batch.
76
+ The unload operation will start, but we have 2 paths for this:
77
+
78
+ 1. unload all the destination packages at the same place
79
+ 2. unload the destination packages one by one at different places
80
+
81
+ By default, if all the move lines have the same destination, the
82
+ first path is used. A flag on the batch picking keeps track of which
83
+ path is used.
84
+ """
85
+
86
+ def test_prepare_unload_all_same_dest(self):
87
+ """All move lines have the same destination location"""
88
+ move_lines = self.move_lines
89
+ self._set_dest_package_and_done(move_lines[:2], self.bin1)
90
+ self._set_dest_package_and_done(move_lines[2:], self.bin2)
91
+ move_lines.write({"location_dest_id": self.packing_location.id})
92
+ response = self.service.dispatch(
93
+ "prepare_unload", params={"picking_batch_id": self.batch.id}
94
+ )
95
+ location = self.packing_location
96
+ data = self._data_for_batch(self.batch, location)
97
+ self.assert_response(
98
+ response,
99
+ next_state="unload_all",
100
+ data=data,
101
+ )
102
+
103
+ def test_prepare_unload_different_dest(self):
104
+ """All move lines have different destination locations"""
105
+ move_lines = self.move_lines
106
+ self._set_dest_package_and_done(move_lines[:2], self.bin1)
107
+ self._set_dest_package_and_done(move_lines[2:], self.bin2)
108
+ move_lines[:2].write({"location_dest_id": self.packing_a_location.id})
109
+ move_lines[2:].write({"location_dest_id": self.packing_b_location.id})
110
+ response = self.service.dispatch(
111
+ "prepare_unload", params={"picking_batch_id": self.batch.id}
112
+ )
113
+ first_line = move_lines[0]
114
+ location = first_line.location_dest_id
115
+ data = self._data_for_batch(self.batch, location, pack=self.bin1)
116
+ self.assert_response(
117
+ response,
118
+ next_state="unload_single",
119
+ data=data,
120
+ )
121
+
122
+
123
+ class ClusterPickingSetDestinationAllCase(ClusterPickingUnloadingCommonCase):
124
+ """Tests covering the /set_destination_all endpoint
125
+
126
+ All the picked lines go to the same destination, a single call to this
127
+ endpoint set them as "unloaded" and set the destination. When the last
128
+ available line of a picking is unloaded, the picking is set to 'done'.
129
+ """
130
+
131
+ def test_set_destination_all_ok(self):
132
+ """Set destination on all lines for the full batch and end the process"""
133
+ move_lines = self.move_lines
134
+ # put destination packages, the whole quantity on lines and a similar
135
+ # destination (when /set_destination_all is called, all the lines to
136
+ # unload must have the same destination)
137
+ self._set_dest_package_and_done(move_lines[:2], self.bin1)
138
+ self._set_dest_package_and_done(move_lines[2:], self.bin2)
139
+ move_lines.write({"location_dest_id": self.packing_location.id})
140
+
141
+ response = self.service.dispatch(
142
+ "set_destination_all",
143
+ params={
144
+ "picking_batch_id": self.batch.id,
145
+ "barcode": self.packing_location.barcode,
146
+ },
147
+ )
148
+ # since the whole batch is complete, we expect the batch and all
149
+ # pickings to be 'done'
150
+ self.assertRecordValues(
151
+ move_lines.mapped("picking_id"), [{"state": "done"}, {"state": "done"}]
152
+ )
153
+ self.assertRecordValues(
154
+ move_lines,
155
+ [
156
+ {
157
+ "shopfloor_unloaded": True,
158
+ "qty_done": 10,
159
+ "state": "done",
160
+ "location_dest_id": self.packing_location.id,
161
+ },
162
+ {
163
+ "shopfloor_unloaded": True,
164
+ "qty_done": 10,
165
+ "state": "done",
166
+ "location_dest_id": self.packing_location.id,
167
+ },
168
+ {
169
+ "shopfloor_unloaded": True,
170
+ "qty_done": 10,
171
+ "state": "done",
172
+ "location_dest_id": self.packing_location.id,
173
+ },
174
+ ],
175
+ )
176
+ self.assertRecordValues(self.batch, [{"state": "done"}])
177
+ self.assert_response(
178
+ response,
179
+ next_state="start",
180
+ message={"message_type": "success", "body": "Batch Transfer complete"},
181
+ )
182
+
183
+ def test_set_destination_all_remaining_lines(self):
184
+ """Set destination on all lines for a part of the batch"""
185
+ # Put destination packages, the whole quantity on lines and a similar
186
+ # destination (when /set_destination_all is called, all the lines to
187
+ # unload must have the same destination).
188
+ # However, we keep a line without qty_done and destination package,
189
+ # so when the dest location is set, the endpoint should route back
190
+ # to the 'start_line' state to work on the remaining line.
191
+ lines_to_unload = self.move_lines[:2]
192
+ self._set_dest_package_and_done(lines_to_unload, self.bin1)
193
+ lines_to_unload.write({"location_dest_id": self.packing_location.id})
194
+
195
+ response = self.service.dispatch(
196
+ "set_destination_all",
197
+ params={
198
+ "picking_batch_id": self.batch.id,
199
+ "barcode": self.packing_location.barcode,
200
+ },
201
+ )
202
+ # Since the whole batch is not complete, state should not be done.
203
+ # The picking with one line should be "done" because we unloaded its line.
204
+ # The second one still has a line to pick.
205
+ self.assertRecordValues(self.one_line_picking, [{"state": "done"}])
206
+ self.assertRecordValues(self.two_lines_picking, [{"state": "assigned"}])
207
+ self.assertRecordValues(
208
+ self.move_lines,
209
+ [
210
+ {
211
+ "shopfloor_unloaded": True,
212
+ "qty_done": 10,
213
+ "state": "done",
214
+ "picking_id": self.one_line_picking.id,
215
+ "location_dest_id": self.packing_location.id,
216
+ },
217
+ {
218
+ "shopfloor_unloaded": True,
219
+ "qty_done": 10,
220
+ # will be done when the second line of the picking is unloaded
221
+ "state": "assigned",
222
+ "picking_id": self.two_lines_picking.id,
223
+ "location_dest_id": self.packing_location.id,
224
+ },
225
+ {
226
+ "shopfloor_unloaded": False,
227
+ "qty_done": 0,
228
+ "state": "assigned",
229
+ "picking_id": self.two_lines_picking.id,
230
+ "location_dest_id": self.packing_location.id,
231
+ },
232
+ ],
233
+ )
234
+ self.assertRecordValues(self.batch, [{"state": "in_progress"}])
235
+
236
+ self.assert_response(
237
+ # the remaining move line still needs to be picked
238
+ response,
239
+ next_state="start_line",
240
+ data=self._line_data(self.move_lines[2]),
241
+ message={"body": "Batch Transfer line done", "message_type": "success"},
242
+ )
243
+
244
+ def test_set_destination_all_picking_unassigned(self):
245
+ """Set destination on lines for some transfers of the batch.
246
+
247
+ The remaining transfers stay as unavailable (confirmed) and are removed
248
+ from the batch when this one is validated.
249
+ The remaining transfers will be processed later in a new batch.
250
+ """
251
+ self.batch.picking_ids.do_unreserve()
252
+ location = self.one_line_picking.location_id
253
+ product = self.one_line_picking.move_ids.product_id
254
+ qty = self.one_line_picking.move_ids.product_uom_qty
255
+ self._update_qty_in_location(location, product, qty)
256
+ self.one_line_picking.action_assign()
257
+ # Prepare lines to process
258
+ lines = self.one_line_picking.move_line_ids
259
+ self._set_dest_package_and_done(lines, self.bin1)
260
+ lines.write({"location_dest_id": self.packing_location.id})
261
+
262
+ response = self.service.dispatch(
263
+ "set_destination_all",
264
+ params={
265
+ "picking_batch_id": self.batch.id,
266
+ "barcode": self.packing_location.barcode,
267
+ },
268
+ )
269
+ # The batch should be done with only one picking.
270
+ # The remaining picking has been removed from the current batch
271
+ self.assertRecordValues(self.one_line_picking, [{"state": "done"}])
272
+ self.assertRecordValues(self.two_lines_picking, [{"state": "confirmed"}])
273
+ self.assertRecordValues(self.batch, [{"state": "done"}])
274
+ self.assertEqual(self.one_line_picking.batch_id, self.batch)
275
+ self.assertFalse(self.two_lines_picking.batch_id)
276
+
277
+ self.assert_response(
278
+ response,
279
+ next_state="start",
280
+ message=self.service.msg_store.batch_transfer_complete(),
281
+ )
282
+
283
+ def test_set_destination_all_but_different_dest(self):
284
+ """Endpoint was called but destinations are different"""
285
+ move_lines = self.move_lines
286
+ self._set_dest_package_and_done(move_lines, self.bin1)
287
+ move_lines[:2].write({"location_dest_id": self.packing_a_location.id})
288
+ move_lines[2:].write({"location_dest_id": self.packing_b_location.id})
289
+
290
+ response = self.service.dispatch(
291
+ "set_destination_all",
292
+ params={
293
+ "picking_batch_id": self.batch.id,
294
+ "barcode": self.packing_location.barcode,
295
+ },
296
+ )
297
+ location = move_lines[0].location_dest_id
298
+ data = self._data_for_batch(self.batch, location, pack=self.bin1)
299
+ self.assert_response(
300
+ response,
301
+ next_state="unload_single",
302
+ data=data,
303
+ )
304
+
305
+ def test_set_destination_all_error_location_not_found(self):
306
+ """Endpoint called with a barcode not existing for a location"""
307
+ move_lines = self.move_lines
308
+ self._set_dest_package_and_done(move_lines, self.bin1)
309
+ move_lines.write({"location_dest_id": self.packing_a_location.id})
310
+
311
+ response = self.service.dispatch(
312
+ "set_destination_all",
313
+ params={"picking_batch_id": self.batch.id, "barcode": "NOTFOUND"},
314
+ )
315
+ location = move_lines[0].location_dest_id
316
+ data = self._data_for_batch(self.batch, location)
317
+ self.assert_response(
318
+ response,
319
+ next_state="unload_all",
320
+ data=data,
321
+ message={
322
+ "message_type": "error",
323
+ "body": "No location found for this barcode.",
324
+ },
325
+ )
326
+
327
+ def test_set_destination_all_error_location_invalid(self):
328
+ """Endpoint called with a barcode for an invalid location
329
+
330
+ It is invalid when the location is not the destination location or
331
+ sublocation of the picking type.
332
+ """
333
+ move_lines = self.move_lines
334
+ self._set_dest_package_and_done(move_lines, self.bin1)
335
+ move_lines.write({"location_dest_id": self.packing_a_location.id})
336
+
337
+ response = self.service.dispatch(
338
+ "set_destination_all",
339
+ params={
340
+ "picking_batch_id": self.batch.id,
341
+ "barcode": self.dispatch_location.barcode,
342
+ },
343
+ )
344
+ location = move_lines[0].location_dest_id
345
+ data = self._data_for_batch(self.batch, location)
346
+ self.assert_response(
347
+ response,
348
+ next_state="unload_all",
349
+ data=data,
350
+ message={"message_type": "error", "body": "You cannot place it here"},
351
+ )
352
+
353
+ def test_set_destination_all_error_location_move_invalid(self):
354
+ """Endpoint called with a barcode for an invalid location
355
+
356
+ It is invalid when the location is not a sublocation of the picking
357
+ or move destination
358
+ """
359
+ move_lines = self.move_lines
360
+ self._set_dest_package_and_done(move_lines, self.bin1)
361
+ move_lines[0].move_id.location_dest_id = self.packing_a_location
362
+ move_lines[0].picking_id.location_dest_id = self.packing_a_location
363
+
364
+ response = self.service.dispatch(
365
+ "set_destination_all",
366
+ params={
367
+ "picking_batch_id": self.batch.id,
368
+ "barcode": self.packing_b_location.barcode,
369
+ },
370
+ )
371
+ location = move_lines[0].location_dest_id
372
+ data = self._data_for_batch(self.batch, location)
373
+ self.assert_response(
374
+ response,
375
+ next_state="unload_all",
376
+ data=data,
377
+ message=self.service.msg_store.dest_location_not_allowed(),
378
+ )
379
+
380
+ def test_set_destination_all_need_confirmation(self):
381
+ """Endpoint called with a barcode for another (valid) location"""
382
+ move_lines = self.move_lines
383
+ self._set_dest_package_and_done(move_lines, self.bin1)
384
+ move_lines.write({"location_dest_id": self.packing_a_location.id})
385
+
386
+ response = self.service.dispatch(
387
+ "set_destination_all",
388
+ params={
389
+ "picking_batch_id": self.batch.id,
390
+ "barcode": self.packing_b_location.barcode,
391
+ },
392
+ )
393
+ location = move_lines[0].location_dest_id
394
+ data = self._data_for_batch(self.batch, location)
395
+ self.assert_response(
396
+ response,
397
+ next_state="confirm_unload_all",
398
+ data=data,
399
+ )
400
+
401
+ def test_set_destination_all_with_confirmation(self):
402
+ """Endpoint called with a barcode for another (valid) location, confirm"""
403
+ move_lines = self.move_lines
404
+ self._set_dest_package_and_done(move_lines, self.bin1)
405
+ move_lines.write({"location_dest_id": self.packing_a_location.id})
406
+
407
+ response = self.service.dispatch(
408
+ "set_destination_all",
409
+ params={
410
+ "picking_batch_id": self.batch.id,
411
+ "barcode": self.packing_b_location.barcode,
412
+ "confirmation": True,
413
+ },
414
+ )
415
+ self.assertRecordValues(
416
+ move_lines,
417
+ [
418
+ {"location_dest_id": self.packing_b_location.id},
419
+ {"location_dest_id": self.packing_b_location.id},
420
+ {"location_dest_id": self.packing_b_location.id},
421
+ ],
422
+ )
423
+ self.assert_response(
424
+ response,
425
+ next_state="start",
426
+ message={"message_type": "success", "body": "Batch Transfer complete"},
427
+ )
428
+
429
+
430
+ class ClusterPickingUnloadSplitCase(ClusterPickingUnloadingCommonCase):
431
+ """Tests covering the /unload_split endpoint
432
+
433
+ All the destinations of the bins were the same so the "unload all" screen
434
+ was presented to the user, but they want different destination, so they hit
435
+ the "split" button. From now on, the workflow should use the "unload single"
436
+ screen even if the destinations are the same.
437
+ """
438
+
439
+ def test_unload_split_ok(self):
440
+ """Call /unload_split and continue to unload single"""
441
+ move_lines = self.move_lines
442
+ # put destination packages, the whole quantity on lines and a similar
443
+ # destination (when /set_destination_all is called, all the lines to
444
+ # unload must have the same destination)
445
+ self._set_dest_package_and_done(move_lines, self.bin1)
446
+ move_lines.write({"location_dest_id": self.packing_location.id})
447
+
448
+ response = self.service.dispatch(
449
+ "unload_split", params={"picking_batch_id": self.batch.id}
450
+ )
451
+ location = move_lines[0].location_dest_id
452
+ data = self._data_for_batch(self.batch, location, pack=self.bin1)
453
+ self.assert_response(
454
+ # the remaining move line still needs to be picked
455
+ response,
456
+ next_state="unload_single",
457
+ data=data,
458
+ )
459
+
460
+
461
+ class ClusterPickingUnloadScanPackCase(ClusterPickingUnloadingCommonCase):
462
+ """Tests covering the /unload_scan_pack endpoint
463
+
464
+ Goods have been put in the package bins, they have different destinations
465
+ or /unload_split has been called, now user has to unload package per
466
+ package. For this, they'll first scan the bin package, which will call the
467
+ endpoint /unload_scan_pack. (second step will be to set the destination
468
+ with /unload_scan_destination, in a different test case)
469
+ """
470
+
471
+ @classmethod
472
+ def setUpClassBaseData(cls, *args, **kwargs):
473
+ super().setUpClassBaseData(*args, **kwargs)
474
+ cls._set_dest_package_and_done(cls.move_lines, cls.bin1)
475
+ cls.move_lines[:2].write({"location_dest_id": cls.packing_a_location.id})
476
+ cls.move_lines[2:].write({"location_dest_id": cls.packing_b_location.id})
477
+
478
+ def test_unload_scan_pack_ok(self):
479
+ """Endpoint /unload_scan_pack is called, result ok"""
480
+ response = self.service.dispatch(
481
+ "unload_scan_pack",
482
+ params={
483
+ "picking_batch_id": self.batch.id,
484
+ "package_id": self.bin1.id,
485
+ "barcode": self.bin1.name,
486
+ },
487
+ )
488
+ location = self.move_lines[0].location_dest_id
489
+ data = self._data_for_batch(self.batch, location, pack=self.bin1)
490
+ self.assert_response(
491
+ response,
492
+ next_state="unload_set_destination",
493
+ data=data,
494
+ )
495
+
496
+ def test_unload_scan_pack_wrong_barcode(self):
497
+ """Endpoint /unload_scan_pack is called, wrong barcode scanned"""
498
+ response = self.service.dispatch(
499
+ "unload_scan_pack",
500
+ params={
501
+ "picking_batch_id": self.batch.id,
502
+ "package_id": self.bin1.id,
503
+ "barcode": self.bin2.name,
504
+ },
505
+ )
506
+ location = self.move_lines[0].location_dest_id
507
+ data = self._data_for_batch(self.batch, location, pack=self.bin1)
508
+ self.assert_response(
509
+ response,
510
+ next_state="unload_single",
511
+ data=data,
512
+ message={"message_type": "error", "body": "Wrong bin"},
513
+ )
514
+
515
+
516
+ class ClusterPickingUnloadScanDestinationCase(ClusterPickingUnloadingCommonCase):
517
+ """Tests covering the /unload_scan_destination endpoint
518
+
519
+ Goods have been put in the package bins, they have different destinations
520
+ or /unload_split has been called, now user has to unload package per
521
+ package. For this, they'll first scanned the bin package already (endpoint
522
+ /unload_scan_pack), now they have to set the destination with
523
+ /unload_scan_destination for the scanned pack.
524
+ """
525
+
526
+ @classmethod
527
+ def setUpClassBaseData(cls, *args, **kwargs):
528
+ super().setUpClassBaseData(*args, **kwargs)
529
+ cls.bin1_lines = cls.move_lines[:1]
530
+ cls.bin2_lines = cls.move_lines[1:]
531
+ cls._set_dest_package_and_done(cls.bin1_lines, cls.bin1)
532
+ cls._set_dest_package_and_done(cls.bin2_lines, cls.bin2)
533
+ cls.bin1_lines.write({"location_dest_id": cls.packing_a_location.id})
534
+ cls.bin2_lines.write({"location_dest_id": cls.packing_b_location.id})
535
+ cls.one_line_picking = cls.batch.picking_ids.filtered(
536
+ lambda picking: len(picking.move_ids) == 1
537
+ )
538
+ cls.two_lines_picking = cls.batch.picking_ids.filtered(
539
+ lambda picking: len(picking.move_ids) == 2
540
+ )
541
+
542
+ def test_unload_scan_destination_ok(self):
543
+ """Endpoint /unload_scan_destination is called, result ok"""
544
+ dest_location = self.bin1_lines[0].location_dest_id
545
+
546
+ response = self.service.dispatch(
547
+ "unload_scan_destination",
548
+ params={
549
+ "picking_batch_id": self.batch.id,
550
+ "package_id": self.bin1.id,
551
+ "barcode": dest_location.barcode,
552
+ },
553
+ )
554
+
555
+ # The scan of destination set 'unloaded' to True to track the fact
556
+ # that we set the destination for the line. In this case, the line
557
+ # and the stock.picking are 'done' because all the lines of the picking
558
+ # have been unloaded
559
+ self.assertRecordValues(self.one_line_picking, [{"state": "done"}])
560
+ self.assertRecordValues(self.two_lines_picking, [{"state": "assigned"}])
561
+ self.assertRecordValues(
562
+ self.bin1_lines,
563
+ [
564
+ {
565
+ "shopfloor_unloaded": True,
566
+ "qty_done": 10,
567
+ "state": "done",
568
+ "picking_id": self.one_line_picking.id,
569
+ "location_dest_id": self.packing_a_location.id,
570
+ }
571
+ ],
572
+ )
573
+ self.assertRecordValues(
574
+ self.bin2_lines,
575
+ [
576
+ {
577
+ "shopfloor_unloaded": False,
578
+ "qty_done": 10,
579
+ "state": "assigned",
580
+ "picking_id": self.two_lines_picking.id,
581
+ "location_dest_id": self.packing_b_location.id,
582
+ },
583
+ {
584
+ "shopfloor_unloaded": False,
585
+ "qty_done": 10,
586
+ "state": "assigned",
587
+ "picking_id": self.two_lines_picking.id,
588
+ "location_dest_id": self.packing_b_location.id,
589
+ },
590
+ ],
591
+ )
592
+ self.assertRecordValues(self.batch, [{"state": "in_progress"}])
593
+
594
+ location = self.bin2_lines[0].location_dest_id
595
+ data = self._data_for_batch(self.batch, location, pack=self.bin2)
596
+ self.assert_response(
597
+ response,
598
+ next_state="unload_single",
599
+ data=data,
600
+ )
601
+
602
+ def test_scan_destination_unload_package_enabled(self):
603
+ """Endpoint /unload_scan_destination is called, unload_package_at_destination
604
+ is enabled, lines.result_package_id should be False"""
605
+ dest_location = self.bin1_lines[0].location_dest_id
606
+ # Default behavior, result_package_id is kept when package in unloaded
607
+ self.service.dispatch(
608
+ "unload_scan_destination",
609
+ params={
610
+ "picking_batch_id": self.batch.id,
611
+ "package_id": self.bin1.id,
612
+ "barcode": dest_location.barcode,
613
+ "confirmation": True,
614
+ },
615
+ )
616
+ self.assertRecordValues(
617
+ self.bin1_lines,
618
+ [
619
+ {
620
+ "result_package_id": self.bin1.id,
621
+ }
622
+ ],
623
+ )
624
+ # Now, if `unload_package_at_destination` is enabled, result_package_id
625
+ # should be set to False
626
+ self.menu.sudo().write({"unload_package_at_destination": True})
627
+ self.service.dispatch(
628
+ "unload_scan_destination",
629
+ params={
630
+ "picking_batch_id": self.batch.id,
631
+ "package_id": self.bin2.id,
632
+ "barcode": dest_location.barcode,
633
+ "confirmation": True,
634
+ },
635
+ )
636
+ self.assertRecordValues(
637
+ self.bin2_lines,
638
+ [
639
+ {
640
+ "result_package_id": False,
641
+ },
642
+ {
643
+ "result_package_id": False,
644
+ },
645
+ ],
646
+ )
647
+
648
+ def test_unload_scan_destination_one_line_of_picking_only(self):
649
+ """Endpoint /unload_scan_destination is called, only one line of picking"""
650
+ # For this test, we assume the move in bin1 is already done.
651
+ self.one_line_picking._action_done()
652
+ # And for the second picking, we put one line bin2 and one line in bin3
653
+ # so the user would have to go through 2 screens for each pack.
654
+ # After scanning and setting the destination for bin2, the picking will
655
+ # still be "assigned" and they'll have to scan bin3 (but this test stops
656
+ # at bin2)
657
+ bin3 = self.env["stock.quant.package"].create({})
658
+ bin2_line = self.bin2_lines[0]
659
+ bin3_line = self.bin2_lines[1]
660
+ self._set_dest_package_and_done(bin3_line, bin3)
661
+
662
+ dest_location = bin2_line.location_dest_id
663
+
664
+ response = self.service.dispatch(
665
+ "unload_scan_destination",
666
+ params={
667
+ "picking_batch_id": self.batch.id,
668
+ "package_id": self.bin2.id,
669
+ "barcode": dest_location.barcode,
670
+ },
671
+ )
672
+
673
+ # The scan of destination set 'unloaded' to True to track the fact
674
+ # that we set the destination for the line. The picking is still
675
+ # assigned because the second line still has to be unloaded.
676
+ self.assertRecordValues(self.two_lines_picking, [{"state": "assigned"}])
677
+ self.assertRecordValues(
678
+ bin2_line,
679
+ [
680
+ {
681
+ "shopfloor_unloaded": True,
682
+ "qty_done": 10,
683
+ "state": "assigned",
684
+ "picking_id": self.two_lines_picking.id,
685
+ "location_dest_id": self.packing_b_location.id,
686
+ }
687
+ ],
688
+ )
689
+ self.assertRecordValues(
690
+ bin3_line,
691
+ [
692
+ {
693
+ "shopfloor_unloaded": False,
694
+ "qty_done": 10,
695
+ "state": "assigned",
696
+ "picking_id": self.two_lines_picking.id,
697
+ "location_dest_id": self.packing_b_location.id,
698
+ }
699
+ ],
700
+ )
701
+ self.assertRecordValues(self.batch, [{"state": "in_progress"}])
702
+ location = bin3_line.location_dest_id
703
+ data = self._data_for_batch(self.batch, location, pack=bin3)
704
+ self.assert_response(
705
+ response,
706
+ next_state="unload_single",
707
+ data=data,
708
+ )
709
+
710
+ def test_unload_scan_destination_last_line(self):
711
+ """Endpoint /unload_scan_destination is called on last line"""
712
+ # For this test, we assume the move in bin1 is already done.
713
+ self.one_line_picking._action_done()
714
+ # And for the second picking, bin2 was already unloaded,
715
+ # remains bin3 to unload.
716
+ bin3 = self.env["stock.quant.package"].create({})
717
+ bin2_line = self.bin2_lines[0]
718
+ bin3_line = self.bin2_lines[1]
719
+ self._set_dest_package_and_done(bin3_line, bin3)
720
+ bin2_line.shopfloor_unloaded = True
721
+
722
+ dest_location = bin3_line.location_dest_id
723
+
724
+ response = self.service.dispatch(
725
+ "unload_scan_destination",
726
+ params={
727
+ "picking_batch_id": self.batch.id,
728
+ "package_id": bin3.id,
729
+ "barcode": dest_location.barcode,
730
+ },
731
+ )
732
+
733
+ # The scan of destination set 'unloaded' to True to track the fact
734
+ # that we set the destination for the line. The picking is done
735
+ # because all the lines have been unloaded
736
+ self.assertRecordValues(self.two_lines_picking, [{"state": "done"}])
737
+ self.assertRecordValues(
738
+ bin3_line,
739
+ [
740
+ {
741
+ "shopfloor_unloaded": True,
742
+ "qty_done": 10,
743
+ "state": "done",
744
+ "picking_id": self.two_lines_picking.id,
745
+ "location_dest_id": self.packing_b_location.id,
746
+ }
747
+ ],
748
+ )
749
+ self.assertRecordValues(self.batch, [{"state": "done"}])
750
+
751
+ self.assert_response(
752
+ response,
753
+ next_state="start",
754
+ message={"body": "Batch Transfer complete", "message_type": "success"},
755
+ )
756
+
757
+ def test_unload_scan_destination_error_location_not_found(self):
758
+ """Endpoint called with a barcode not existing for a location"""
759
+ response = self.service.dispatch(
760
+ "unload_scan_destination",
761
+ params={
762
+ "picking_batch_id": self.batch.id,
763
+ "package_id": self.bin1.id,
764
+ "barcode": "¤",
765
+ },
766
+ )
767
+ location = self.bin1_lines[0].location_dest_id
768
+ data = self._data_for_batch(self.batch, location, pack=self.bin1)
769
+ self.assert_response(
770
+ response,
771
+ next_state="unload_set_destination",
772
+ data=data,
773
+ message={
774
+ "message_type": "error",
775
+ "body": "No location found for this barcode.",
776
+ },
777
+ )
778
+
779
+ def test_unload_scan_destination_error_location_invalid(self):
780
+ """Endpoint called with a barcode for an invalid location
781
+
782
+ It is invalid when the location is not the destination location or
783
+ sublocation of the picking type.
784
+ """
785
+ response = self.service.dispatch(
786
+ "unload_scan_destination",
787
+ params={
788
+ "picking_batch_id": self.batch.id,
789
+ "package_id": self.bin1.id,
790
+ "barcode": self.dispatch_location.barcode,
791
+ },
792
+ )
793
+ location = self.bin1_lines[0].location_dest_id
794
+ data = self._data_for_batch(self.batch, location, pack=self.bin1)
795
+ self.assert_response(
796
+ response,
797
+ next_state="unload_set_destination",
798
+ data=data,
799
+ message={"message_type": "error", "body": "You cannot place it here"},
800
+ )
801
+
802
+ def test_unload_scan_destination_error_location_move_invalid(self):
803
+ """Endpoint called with a barcode for an invalid location
804
+
805
+ It is invalid when the location is not a sublocation of the picking
806
+ or move destination
807
+ """
808
+ self.bin1_lines[0].picking_id.location_dest_id = self.packing_a_location
809
+ response = self.service.dispatch(
810
+ "unload_scan_destination",
811
+ params={
812
+ "picking_batch_id": self.batch.id,
813
+ "package_id": self.bin1.id,
814
+ "barcode": self.packing_b_location.barcode,
815
+ },
816
+ )
817
+ location = self.bin1_lines[0].location_dest_id
818
+ data = self._data_for_batch(self.batch, location, pack=self.bin1)
819
+ self.assert_response(
820
+ response,
821
+ next_state="unload_set_destination",
822
+ data=data,
823
+ message=self.service.msg_store.dest_location_not_allowed(),
824
+ )
825
+
826
+ def test_unload_scan_destination_need_confirmation(self):
827
+ """Endpoint called with a barcode for another (valid) location"""
828
+ response = self.service.dispatch(
829
+ "unload_scan_destination",
830
+ params={
831
+ "picking_batch_id": self.batch.id,
832
+ "package_id": self.bin1.id,
833
+ "barcode": self.packing_b_location.barcode,
834
+ },
835
+ )
836
+ location = self.bin1_lines[0].location_dest_id
837
+ data = self._data_for_batch(self.batch, location, pack=self.bin1)
838
+ self.assert_response(
839
+ response,
840
+ next_state="confirm_unload_set_destination",
841
+ data=data,
842
+ )
843
+
844
+ def test_unload_scan_destination_with_confirmation(self):
845
+ """Endpoint called with a barcode for another (valid) location, confirm"""
846
+ response = self.service.dispatch(
847
+ "unload_scan_destination",
848
+ params={
849
+ "picking_batch_id": self.batch.id,
850
+ "package_id": self.bin2.id,
851
+ "barcode": self.packing_a_location.barcode,
852
+ "confirmation": True,
853
+ },
854
+ )
855
+ self.assertRecordValues(
856
+ self.bin2.quant_ids,
857
+ [
858
+ {"location_id": self.packing_a_location.id},
859
+ {"location_id": self.packing_a_location.id},
860
+ ],
861
+ )
862
+ self.assertRecordValues(
863
+ self.two_lines_picking.move_line_ids,
864
+ [
865
+ {"location_dest_id": self.packing_a_location.id},
866
+ {"location_dest_id": self.packing_a_location.id},
867
+ ],
868
+ )
869
+ self.assert_response(response, next_state="unload_single", data=self.ANY)
870
+
871
+ def test_unload_scan_destination_completion_info(self):
872
+ """/unload_scan_destination that make chained picking ready"""
873
+ picking = self.one_line_picking
874
+ dest_location = picking.move_line_ids.location_dest_id
875
+ self.picking_type.sudo().display_completion_info = True
876
+
877
+ # create a chained picking after the current one
878
+ next_picking = picking.copy(
879
+ {
880
+ "picking_type_id": self.wh.out_type_id.id,
881
+ "location_id": dest_location.id,
882
+ "location_dest_id": self.customer_location.id,
883
+ }
884
+ )
885
+ next_picking.move_ids.write(
886
+ {
887
+ "move_orig_ids": [(6, 0, picking.move_ids.ids)],
888
+ "location_id": dest_location.id,
889
+ }
890
+ )
891
+ next_picking.action_confirm()
892
+
893
+ response = self.service.dispatch(
894
+ "unload_scan_destination",
895
+ params={
896
+ "picking_batch_id": self.batch.id,
897
+ "package_id": self.bin1.id,
898
+ "barcode": dest_location.barcode,
899
+ },
900
+ )
901
+ location = self.bin2_lines[0].location_dest_id
902
+ data = self._data_for_batch(self.batch, location, pack=self.bin2)
903
+ self.assert_response(
904
+ response,
905
+ next_state="unload_single",
906
+ popup={
907
+ "body": "Last operation of transfer {}. Next operation "
908
+ "({}) is ready to proceed.".format(picking.name, next_picking.name)
909
+ },
910
+ data=data,
911
+ )