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,748 @@
1
+ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+
4
+ from .test_location_content_transfer_base import LocationContentTransferCommonCase
5
+
6
+ # pylint: disable=missing-return
7
+
8
+
9
+ class LocationContentTransferSingleCase(LocationContentTransferCommonCase):
10
+ """Tests for endpoint used from state start_single
11
+
12
+ * /scan_package
13
+ * /scan_line
14
+ * /postpone_package
15
+ * /postpone_line
16
+
17
+ """
18
+
19
+ # TODO common with set_destination_all?
20
+ @classmethod
21
+ def setUpClassBaseData(cls):
22
+ super().setUpClassBaseData()
23
+ products = cls.product_a + cls.product_b + cls.product_c + cls.product_d
24
+ for product in products:
25
+ cls.env["stock.putaway.rule"].sudo().create(
26
+ {
27
+ "product_id": product.id,
28
+ "location_in_id": cls.stock_location.id,
29
+ "location_out_id": cls.shelf1.id,
30
+ }
31
+ )
32
+
33
+ cls.product_d.tracking = "lot"
34
+ cls.picking1 = picking1 = cls._create_picking(
35
+ lines=[(cls.product_a, 10), (cls.product_b, 10)]
36
+ )
37
+ cls.picking2 = picking2 = cls._create_picking(
38
+ lines=[(cls.product_c, 10), (cls.product_d, 10)]
39
+ )
40
+ cls.pickings = picking1 | picking2
41
+ cls._fill_stock_for_moves(
42
+ picking1.move_ids, in_package=True, location=cls.content_loc
43
+ )
44
+ cls.product_d_lot = cls.env["stock.lot"].create(
45
+ {"product_id": cls.product_d.id, "company_id": cls.env.company.id}
46
+ )
47
+ cls._fill_stock_for_moves(picking2.move_ids[0], location=cls.content_loc)
48
+ cls._fill_stock_for_moves(
49
+ picking2.move_ids[1], location=cls.content_loc, in_lot=cls.product_d_lot
50
+ )
51
+ cls.pickings.action_assign()
52
+ cls._simulate_pickings_selected(cls.pickings)
53
+
54
+ def _test_scan_package_ok(self, barcode):
55
+ package_level = self.picking1.move_line_ids.package_level_id
56
+ response = self.service.dispatch(
57
+ "scan_package",
58
+ params={
59
+ "location_id": self.content_loc.id,
60
+ "package_level_id": package_level.id,
61
+ "barcode": barcode,
62
+ },
63
+ )
64
+ self.assert_response_scan_destination(response, package_level)
65
+
66
+ def test_scan_package_location_not_found(self):
67
+ response = self.service.dispatch(
68
+ "scan_package",
69
+ params={
70
+ "location_id": 1234567890, # Doesn't exist
71
+ "package_level_id": 42,
72
+ "barcode": "TEST",
73
+ },
74
+ )
75
+ self.assert_response_start(
76
+ response, message=self.service.msg_store.record_not_found()
77
+ )
78
+
79
+ def test_scan_package_package_ok(self):
80
+ package_level = self.picking1.move_line_ids.package_level_id
81
+ self._test_scan_package_ok(package_level.package_id.name)
82
+
83
+ def _scan_package_error(self, package_level, scanned, message):
84
+ response = self.service.dispatch(
85
+ "scan_package",
86
+ params={
87
+ "location_id": self.content_loc.id,
88
+ "package_level_id": package_level.id,
89
+ "barcode": scanned,
90
+ },
91
+ )
92
+ self.assert_response_start_single(response, self.pickings, message=message)
93
+
94
+ def test_scan_package_error_wrong_package(self):
95
+ """Wrong package scanned"""
96
+ pack = self.env["stock.quant.package"].sudo().create({})
97
+ self._scan_package_error(
98
+ self.picking1.move_line_ids.package_level_id,
99
+ pack.name,
100
+ {"message_type": "error", "body": "Wrong pack."},
101
+ )
102
+
103
+ def test_scan_package_error_wrong_product(self):
104
+ """Wrong product scanned"""
105
+ product = (
106
+ self.env["product.product"]
107
+ .sudo()
108
+ .create(
109
+ {
110
+ "name": "Wrong",
111
+ "barcode": "WRONGPRODUCT",
112
+ }
113
+ )
114
+ )
115
+ self._scan_package_error(
116
+ self.picking1.move_line_ids.package_level_id,
117
+ product.barcode,
118
+ {"message_type": "error", "body": "Wrong product."},
119
+ )
120
+
121
+ def test_scan_package_error_wrong_lot(self):
122
+ """Wrong product scanned"""
123
+ lot = (
124
+ self.env["stock.lot"]
125
+ .sudo()
126
+ .create(
127
+ {
128
+ "name": "WRONGLOT",
129
+ "product_id": self.picking1.move_line_ids[0].product_id.id,
130
+ "company_id": self.env.company.id,
131
+ }
132
+ )
133
+ )
134
+ self._scan_package_error(
135
+ self.picking1.move_line_ids.package_level_id,
136
+ lot.name,
137
+ {"message_type": "error", "body": "Wrong lot."},
138
+ )
139
+
140
+ def test_scan_package_barcode_not_found(self):
141
+ """Nothing found for the barcode"""
142
+ self._scan_package_error(
143
+ self.picking1.move_line_ids.package_level_id,
144
+ "NO_EXISTING_BARCODE",
145
+ {"message_type": "error", "body": "Barcode not found"},
146
+ )
147
+
148
+ def test_scan_package_product_ok(self):
149
+ # product_a is in the package and anywhere else so it's
150
+ # accepted to check we scanned the correct package
151
+ self._test_scan_package_ok(self.product_a.barcode)
152
+
153
+ def test_scan_package_product_packaging_ok(self):
154
+ # product_a is in the package and anywhere else so it's
155
+ # accepted to check we scanned the correct package
156
+ self._test_scan_package_ok(self.product_a.packaging_ids[0].barcode)
157
+
158
+ def test_scan_package_lot_ok(self):
159
+ package_level = self.picking1.move_line_ids.package_level_id
160
+ line_product_a = package_level.move_line_ids[0]
161
+ self.product_a.tracking = "lot"
162
+ line_product_a.lot_id = self.env["stock.lot"].create(
163
+ {"product_id": self.product_a.id, "company_id": self.env.company.id}
164
+ )
165
+ # lot of product_a is in the package and anywhere else so it's
166
+ # accepted to check we scanned the correct package
167
+ self._test_scan_package_ok(line_product_a.lot_id.name)
168
+
169
+ def _test_scan_package_nok(self, pickings, barcode, message):
170
+ package_level = self.picking1.move_line_ids.package_level_id
171
+ response = self.service.dispatch(
172
+ "scan_package",
173
+ params={
174
+ "location_id": self.content_loc.id,
175
+ "package_level_id": package_level.id,
176
+ "barcode": barcode,
177
+ },
178
+ )
179
+ self.assert_response_start_single(response, pickings, message=message)
180
+
181
+ def test_scan_package_product_nok_different_package(self):
182
+ # add another picking with a package with product a,
183
+ # if we scan product A, we can't know for which package it is
184
+ picking = self._create_picking(lines=[(self.product_a, 10)])
185
+ self._fill_stock_for_moves(
186
+ picking.move_ids, in_package=True, location=self.content_loc
187
+ )
188
+ picking.action_assign()
189
+ self._simulate_pickings_selected(picking)
190
+ self._test_scan_package_nok(
191
+ self.pickings | picking,
192
+ self.product_a.barcode,
193
+ {"message_type": "error", "body": "Scan the package"},
194
+ )
195
+
196
+ def test_scan_package_product_nok_different_line(self):
197
+ # add another picking with a raw line with product a,
198
+ # if we scan product A, we can't know which line/package we want
199
+ picking = self._create_picking(lines=[(self.product_a, 10)])
200
+ self._fill_stock_for_moves(picking.move_ids, location=self.content_loc)
201
+ picking.action_assign()
202
+ self._simulate_pickings_selected(picking)
203
+ self._test_scan_package_nok(
204
+ self.pickings | picking,
205
+ self.product_a.barcode,
206
+ {"message_type": "error", "body": "Scan the package"},
207
+ )
208
+
209
+ def test_scan_package_product_nok_product_tracked(self):
210
+ # we scan product_a's barcode but it's tracked by lot
211
+ self.product_a.tracking = "lot"
212
+ self._test_scan_package_nok(
213
+ self.pickings,
214
+ self.product_a.barcode,
215
+ {"message_type": "error", "body": "Scan the package"},
216
+ )
217
+
218
+ def test_scan_package_lot_nok_different_package(self):
219
+ # add another picking with a package with the lot used in our package,
220
+ # if we scan the lot, we can't know for which package it is
221
+ package_level = self.picking1.move_line_ids.package_level_id
222
+ line_product_a = package_level.move_line_ids[0]
223
+ self.product_a.tracking = "lot"
224
+ line_product_a.lot_id = lot = self.env["stock.lot"].create(
225
+ {"product_id": self.product_a.id, "company_id": self.env.company.id}
226
+ )
227
+ picking = self._create_picking(lines=[(self.product_a, 10)])
228
+ self._fill_stock_for_moves(
229
+ picking.move_ids, in_package=True, in_lot=lot, location=self.content_loc
230
+ )
231
+ picking.action_assign()
232
+ self._simulate_pickings_selected(picking)
233
+ self._test_scan_package_nok(
234
+ self.pickings | picking,
235
+ self.product_a.barcode,
236
+ {"message_type": "error", "body": "Scan the package"},
237
+ )
238
+
239
+ def test_scan_package_lot_nok_different_line(self):
240
+ # add another picking with a raw line with a lot used in our package,
241
+ # if we scan the lot, we can't know which line/package we want
242
+ package_level = self.picking1.move_line_ids.package_level_id
243
+ line_product_a = package_level.move_line_ids[0]
244
+ self.product_a.tracking = "lot"
245
+ line_product_a.lot_id = lot = self.env["stock.lot"].create(
246
+ {"product_id": self.product_a.id, "company_id": self.env.company.id}
247
+ )
248
+ picking = self._create_picking(lines=[(self.product_a, 10)])
249
+ self._fill_stock_for_moves(
250
+ picking.move_ids, in_lot=lot, location=self.content_loc
251
+ )
252
+ picking.action_assign()
253
+ self._simulate_pickings_selected(picking)
254
+ self._test_scan_package_nok(
255
+ self.pickings | picking,
256
+ self.product_a.barcode,
257
+ {"message_type": "error", "body": "Scan the package"},
258
+ )
259
+
260
+ def test_scan_package_package_level_not_exists(self):
261
+ package_level = self.picking1.move_line_ids.package_level_id
262
+ package_level_id = package_level.id
263
+ package_level.unlink()
264
+ response = self.service.dispatch(
265
+ "scan_package",
266
+ params={
267
+ "location_id": self.content_loc.id,
268
+ "package_level_id": package_level_id,
269
+ "barcode": self.product_a.barcode,
270
+ },
271
+ )
272
+ self.assert_response_start_single(
273
+ response, self.pickings, message=self.service.msg_store.record_not_found()
274
+ )
275
+
276
+ def _test_scan_line_ok(self, move_line, barcode):
277
+ response = self.service.dispatch(
278
+ "scan_line",
279
+ params={
280
+ "location_id": self.content_loc.id,
281
+ "move_line_id": move_line.id,
282
+ "barcode": barcode,
283
+ },
284
+ )
285
+ self.assert_response_scan_destination(response, move_line)
286
+
287
+ def test_scan_line_package_ok(self):
288
+ move_line = self.picking2.move_line_ids[0]
289
+ package = move_line.package_id = self.env["stock.quant.package"].create({})
290
+ self._test_scan_line_ok(move_line, package.name)
291
+
292
+ def test_scan_line_product_ok(self):
293
+ move_line = self.picking2.move_line_ids[0]
294
+ # check we selected the good line
295
+ self.assertEqual(move_line.product_id, self.product_c)
296
+ self._test_scan_line_ok(move_line, self.product_c.barcode)
297
+
298
+ def test_scan_line_product_packaging_ok(self):
299
+ move_line = self.picking2.move_line_ids[0]
300
+ # check we selected the good line
301
+ self.assertEqual(move_line.product_id, self.product_c)
302
+ self._test_scan_line_ok(move_line, self.product_c.packaging_ids[0].barcode)
303
+
304
+ def test_scan_line_lot_ok(self):
305
+ move_line = self.picking2.move_line_ids[1]
306
+ # check we selected the good line (the one with a lot)
307
+ self.assertEqual(move_line.product_id, self.product_d)
308
+ self._test_scan_line_ok(move_line, self.product_d_lot.name)
309
+
310
+ def _test_scan_line_nok(self, pickings, move_line_id, barcode, message):
311
+ response = self.service.dispatch(
312
+ "scan_line",
313
+ params={
314
+ "location_id": self.content_loc.id,
315
+ "move_line_id": move_line_id,
316
+ "barcode": barcode,
317
+ },
318
+ )
319
+ self.assert_response_start_single(response, pickings, message=message)
320
+
321
+ def test_scan_line_product_nok_product_tracked(self):
322
+ # we scan product_d's barcode but it's tracked by lot
323
+ move_line = self.picking2.move_line_ids[1]
324
+ # check we selected the good line (the one with a lot)
325
+ self.assertEqual(move_line.product_id, self.product_d)
326
+ self._test_scan_line_nok(
327
+ self.pickings,
328
+ move_line.id,
329
+ self.product_d.barcode,
330
+ self.service.msg_store.scan_lot_on_product_tracked_by_lot(),
331
+ )
332
+
333
+ def test_scan_line_error_wrong_package(self):
334
+ """Wrong package scanned"""
335
+ move_line = self.picking2.move_line_ids[0]
336
+ pack = self.env["stock.quant.package"].sudo().create({})
337
+ self._test_scan_line_nok(
338
+ self.pickings,
339
+ move_line.id,
340
+ pack.name,
341
+ {"message_type": "error", "body": "Wrong pack."},
342
+ )
343
+
344
+ def test_scan_line_error_wrong_product(self):
345
+ """Wrong product scanned"""
346
+ move_line = self.picking2.move_line_ids[0]
347
+ product = (
348
+ self.env["product.product"]
349
+ .sudo()
350
+ .create(
351
+ {
352
+ "name": "Wrong",
353
+ "barcode": "WRONGPRODUCT",
354
+ }
355
+ )
356
+ )
357
+ self._test_scan_line_nok(
358
+ self.pickings,
359
+ move_line.id,
360
+ product.barcode,
361
+ {"message_type": "error", "body": "Wrong product."},
362
+ )
363
+
364
+ def test_scan_line_error_wrong_lot(self):
365
+ """Wrong product scanned"""
366
+ move_line = self.picking2.move_line_ids[0]
367
+ lot = (
368
+ self.env["stock.lot"]
369
+ .sudo()
370
+ .create(
371
+ {
372
+ "name": "WRONGLOT",
373
+ "product_id": move_line.product_id.id,
374
+ "company_id": self.env.company.id,
375
+ }
376
+ )
377
+ )
378
+ self._test_scan_line_nok(
379
+ self.pickings,
380
+ move_line.id,
381
+ lot.name,
382
+ {"message_type": "error", "body": "Wrong lot."},
383
+ )
384
+
385
+ def test_scan_line_barcode_not_found(self):
386
+ move_line = self.picking2.move_line_ids[0]
387
+ self._test_scan_line_nok(
388
+ self.pickings,
389
+ move_line.id,
390
+ "NOT_FOUND",
391
+ self.service.msg_store.barcode_not_found(),
392
+ )
393
+
394
+ def test_scan_line_move_line_not_exists(self):
395
+ move_line = self.picking2.move_line_ids[0]
396
+ move_line_id = move_line.id
397
+ move_line.unlink()
398
+ self._test_scan_line_nok(
399
+ self.pickings,
400
+ move_line_id,
401
+ "NOT_FOUND",
402
+ self.service.msg_store.record_not_found(),
403
+ )
404
+
405
+ def test_postpone_package_wrong_parameters(self):
406
+ """Wrong 'location_id' and 'package_level_id' parameters, redirect the
407
+ user to the 'start' screen.
408
+ """
409
+ package_level = self.picking1.move_line_ids.package_level_id
410
+ response = self.service.dispatch(
411
+ "postpone_package",
412
+ params={
413
+ "location_id": 1234567890, # Doesn't exist
414
+ "package_level_id": package_level.id,
415
+ },
416
+ )
417
+ self.assert_response_start(
418
+ response, message=self.service.msg_store.record_not_found()
419
+ )
420
+ response = self.service.dispatch(
421
+ "postpone_package",
422
+ params={
423
+ "location_id": self.content_loc.id,
424
+ "package_level_id": 1234567890, # Doesn't exist
425
+ },
426
+ )
427
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
428
+ self.assert_response_start_single(
429
+ response,
430
+ move_lines.mapped("picking_id"),
431
+ )
432
+
433
+ def test_postpone_package_ok(self):
434
+ package_level = self.picking1.move_line_ids.package_level_id
435
+ previous_priority = package_level.shopfloor_priority
436
+ self.assertFalse(package_level.shopfloor_postponed)
437
+ response = self.service.dispatch(
438
+ "postpone_package",
439
+ params={
440
+ "location_id": self.content_loc.id,
441
+ "package_level_id": package_level.id,
442
+ },
443
+ )
444
+ self.assertTrue(package_level.shopfloor_postponed)
445
+ self.assertEqual(package_level.shopfloor_priority, previous_priority + 1)
446
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
447
+ self.assert_response_start_single(
448
+ response,
449
+ move_lines.mapped("picking_id"),
450
+ )
451
+
452
+ def test_postpone_sorter(self):
453
+ move_line = self.picking2.move_line_ids[0]
454
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
455
+ pickings = move_lines.mapped("picking_id")
456
+ sorter = self.service._actions_for("location_content_transfer.sorter")
457
+ sorter.feed_pickings(pickings)
458
+ content_sorted1 = list(sorter)
459
+ self.service.dispatch(
460
+ "postpone_line",
461
+ params={"location_id": self.content_loc.id, "move_line_id": move_line.id},
462
+ )
463
+ sorter.sort()
464
+ content_sorted2 = list(sorter)
465
+ self.assertTrue(content_sorted1 != content_sorted2)
466
+
467
+ def test_postpone_line_wrong_parameters(self):
468
+ """Wrong 'location_id' and 'move_line_id' parameters, redirect the
469
+ user to the 'start' screen.
470
+ """
471
+ move_line = self.picking2.move_line_ids[0]
472
+ response = self.service.dispatch(
473
+ "postpone_line",
474
+ params={
475
+ "location_id": 1234567890, # Doesn't exist
476
+ "move_line_id": move_line.id,
477
+ },
478
+ )
479
+ self.assert_response_start(
480
+ response, message=self.service.msg_store.record_not_found()
481
+ )
482
+ response = self.service.dispatch(
483
+ "postpone_line",
484
+ params={
485
+ "location_id": self.content_loc.id,
486
+ "move_line_id": 1234567890, # Doesn't exist
487
+ },
488
+ )
489
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
490
+ self.assert_response_start_single(
491
+ response,
492
+ move_lines.mapped("picking_id"),
493
+ )
494
+
495
+ def test_postpone_line_ok(self):
496
+ move_line = self.picking2.move_line_ids[0]
497
+ previous_priority = move_line.shopfloor_priority
498
+ self.assertFalse(move_line.shopfloor_postponed)
499
+ response = self.service.dispatch(
500
+ "postpone_line",
501
+ params={"location_id": self.content_loc.id, "move_line_id": move_line.id},
502
+ )
503
+ self.assertTrue(move_line.shopfloor_postponed)
504
+ self.assertEqual(move_line.shopfloor_priority, previous_priority + 1)
505
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
506
+ self.assert_response_start_single(
507
+ response,
508
+ move_lines.mapped("picking_id"),
509
+ )
510
+
511
+ def test_stock_out_package_wrong_parameters(self):
512
+ """Wrong 'location_id' and 'package_level_id' parameters, redirect the
513
+ user to the 'start' screen.
514
+ """
515
+ package_level = self.picking1.move_line_ids.package_level_id
516
+ response = self.service.dispatch(
517
+ "stock_out_package",
518
+ params={
519
+ "location_id": 1234567890, # Doesn't exist
520
+ "package_level_id": package_level.id,
521
+ },
522
+ )
523
+ self.assert_response_start(
524
+ response, message=self.service.msg_store.record_not_found()
525
+ )
526
+ response = self.service.dispatch(
527
+ "stock_out_package",
528
+ params={
529
+ "location_id": self.content_loc.id,
530
+ "package_level_id": 1234567890, # Doesn't exist
531
+ },
532
+ )
533
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
534
+ self.assert_response_start_single(
535
+ response,
536
+ move_lines.mapped("picking_id"),
537
+ )
538
+
539
+ def test_stock_out_package_ok(self):
540
+ """Declare a stock out on a package_level."""
541
+ package_level = self.picking1.move_line_ids.package_level_id
542
+ response = self.service.dispatch(
543
+ "stock_out_package",
544
+ params={
545
+ "location_id": self.content_loc.id,
546
+ "package_level_id": package_level.id,
547
+ },
548
+ )
549
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
550
+ self.assert_response_start_single(
551
+ response,
552
+ move_lines.mapped("picking_id"),
553
+ )
554
+
555
+ def test_stock_out_line_wrong_parameters(self):
556
+ """Wrong 'location_id' and 'move_line_id' parameters, redirect the
557
+ user to the 'start' screen.
558
+ """
559
+ move_line = self.picking2.move_line_ids[0]
560
+ response = self.service.dispatch(
561
+ "stock_out_line",
562
+ params={
563
+ "location_id": 1234567890, # Doesn't exist
564
+ "move_line_id": move_line.id,
565
+ },
566
+ )
567
+ self.assert_response_start(
568
+ response, message=self.service.msg_store.record_not_found()
569
+ )
570
+ response = self.service.dispatch(
571
+ "stock_out_line",
572
+ params={
573
+ "location_id": self.content_loc.id,
574
+ "move_line_id": 1234567890, # Doesn't exist
575
+ },
576
+ )
577
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
578
+ self.assert_response_start_single(
579
+ response,
580
+ move_lines.mapped("picking_id"),
581
+ )
582
+
583
+ def test_dismiss_package_level_ok(self):
584
+ """Open a package level"""
585
+ package_level = self.picking1.move_line_ids.package_level_id
586
+ move_lines = package_level.move_line_ids
587
+ response = self.service.dispatch(
588
+ "dismiss_package_level",
589
+ params={
590
+ "location_id": self.content_loc.id,
591
+ "package_level_id": package_level.id,
592
+ },
593
+ )
594
+ self.assertFalse(package_level.exists())
595
+ self.assertFalse(move_lines.result_package_id)
596
+ self.assertFalse(move_lines.package_level_id)
597
+ self.assertEqual(move_lines.mapped("shopfloor_priority"), [1, 1])
598
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
599
+ self.assert_response_start_single(
600
+ response,
601
+ move_lines.mapped("picking_id"),
602
+ message=self.service.msg_store.package_open(),
603
+ )
604
+
605
+ def test_dismiss_package_level_error_no_package_level(self):
606
+ """Open a package level, send unknown package level id"""
607
+ response = self.service.dispatch(
608
+ "dismiss_package_level",
609
+ params={"location_id": self.content_loc.id, "package_level_id": 0},
610
+ )
611
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
612
+ self.assert_response_start_single(
613
+ response,
614
+ move_lines.mapped("picking_id"),
615
+ message=self.service.msg_store.record_not_found(),
616
+ )
617
+
618
+ def test_dismiss_package_level_error_no_location(self):
619
+ """Open a package level, send unknown location id"""
620
+ package_level = self.picking1.move_line_ids.package_level_id
621
+ response = self.service.dispatch(
622
+ "dismiss_package_level",
623
+ params={"location_id": 0, "package_level_id": package_level.id},
624
+ )
625
+ self.assert_response_start(
626
+ response,
627
+ message=self.service.msg_store.record_not_found(),
628
+ )
629
+
630
+
631
+ class LocationContentTransferSingleSpecialCase(LocationContentTransferCommonCase):
632
+ """Tests for endpoint used from state start_single (special cases)
633
+
634
+ * /stock_out_package
635
+ * /stock_out_line
636
+
637
+ """
638
+
639
+ @classmethod
640
+ def setUpClassBaseData(cls):
641
+ super().setUpClassBaseData()
642
+ products = cls.product_a | cls.product_b
643
+ for product in products:
644
+ cls.env["stock.putaway.rule"].sudo().create(
645
+ {
646
+ "product_id": product.id,
647
+ "location_in_id": cls.stock_location.id,
648
+ "location_out_id": cls.shelf1.id,
649
+ }
650
+ )
651
+
652
+ cls.picking = cls._create_picking(
653
+ lines=[(cls.product_a, 10), (cls.product_b, 10)]
654
+ )
655
+ cls.move_product_a = cls.picking.move_ids.filtered(
656
+ lambda m: m.product_id == cls.product_a
657
+ )
658
+ cls.move_product_b = cls.picking.move_ids.filtered(
659
+ lambda m: m.product_id == cls.product_b
660
+ )
661
+ # Change the initial demand of product_a to get two move lines for
662
+ # reserved qties:
663
+ # - 10 from the package
664
+ # - 5 from the qty without package
665
+ cls._fill_stock_for_moves(
666
+ cls.move_product_a, in_package=True, location=cls.content_loc
667
+ )
668
+ cls.move_product_a.product_uom_qty = 15
669
+ cls._update_qty_in_location(
670
+ cls.content_loc,
671
+ cls.product_a,
672
+ 5,
673
+ )
674
+ # Put product_b quantities in two different source locations to get
675
+ # two stock move lines (6 and 4 to satisfy 10 qties)
676
+ cls._update_qty_in_location(cls.picking.location_id, cls.product_b, 6)
677
+ cls._update_qty_in_location(cls.content_loc, cls.product_b, 4)
678
+ # Reserve quantities
679
+ cls.picking.action_assign()
680
+ cls._simulate_pickings_selected(cls.picking)
681
+
682
+ def test_stock_out_package_split_move(self):
683
+ """Declare a stock out on a package_level related to moves containing
684
+ other unrelated move lines.
685
+ """
686
+ package_level = self.picking.move_line_ids.package_level_id
687
+ self.assertEqual(self.product_a.qty_available, 15)
688
+ response = self.service.dispatch(
689
+ "stock_out_package",
690
+ params={
691
+ "location_id": self.content_loc.id,
692
+ "package_level_id": package_level.id,
693
+ },
694
+ )
695
+ # Check the picking data
696
+ self.assertFalse(package_level.exists())
697
+ moves_product_a = self.picking.move_ids.filtered(
698
+ lambda m: m.product_id == self.product_a
699
+ )
700
+ self.assertEqual(len(moves_product_a), 2)
701
+ move_product_a = moves_product_a.filtered(
702
+ lambda m: m.state not in ("cancel", "done")
703
+ )
704
+ self.assertEqual(len(move_product_a), 1)
705
+ self.assertEqual(move_product_a.state, "assigned")
706
+ self.assertEqual(len(move_product_a.move_line_ids), 1)
707
+ self.assertEqual(self.product_a.qty_available, 5)
708
+ # Check the response
709
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
710
+ self.assert_response_start_single(
711
+ response,
712
+ move_lines.mapped("picking_id"),
713
+ )
714
+
715
+ def test_stock_out_line_split_move(self):
716
+ """Declare a stock out on a move line related to moves containing
717
+ other move lines.
718
+ """
719
+ self.assertEqual(len(self.picking.move_ids), 2)
720
+ self.assertEqual(len(self.move_product_b.move_line_ids), 2)
721
+ move_line = self.move_product_b.move_line_ids.filtered(
722
+ lambda ml: ml.reserved_uom_qty == 4 # 4/10 to stock out
723
+ )
724
+ self.assertEqual(self.product_b.qty_available, 10)
725
+ response = self.service.dispatch(
726
+ "stock_out_line",
727
+ params={"location_id": self.content_loc.id, "move_line_id": move_line.id},
728
+ )
729
+ # Check the picking data
730
+ self.assertFalse(move_line.exists())
731
+ moves_product_b = self.picking.move_ids.filtered(
732
+ lambda m: m.product_id == self.product_b
733
+ )
734
+ self.assertEqual(len(moves_product_b), 2)
735
+ move_product_b = moves_product_b.filtered(
736
+ lambda m: m.state not in ("cancel", "done")
737
+ )
738
+ self.assertEqual(len(move_product_b), 1)
739
+ self.assertEqual(move_product_b.state, "assigned")
740
+ self.assertEqual(len(move_product_b.move_line_ids), 1)
741
+
742
+ self.assertEqual(self.product_b.qty_available, 6)
743
+ # Check the response
744
+ move_lines = self.service._find_transfer_move_lines(self.content_loc)
745
+ self.assert_response_start_single(
746
+ response,
747
+ move_lines.mapped("picking_id"),
748
+ )