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,723 @@
1
+ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+ from odoo import fields
4
+
5
+ from .test_zone_picking_base import ZonePickingCommonCase
6
+
7
+
8
+ # pylint: disable=missing-return
9
+ class ZonePickingSelectLineCase(ZonePickingCommonCase):
10
+ """Tests for endpoint used from select_line
11
+
12
+ * /list_move_lines (to change order)
13
+ * /scan_source
14
+ * /prepare_unload
15
+
16
+ """
17
+
18
+ def setUp(self):
19
+ super().setUp()
20
+ self.service.work.current_picking_type = self.picking1.picking_type_id
21
+
22
+ def test_list_move_lines_order(self):
23
+ self.zone_sublocation2.name = "AAA " + self.zone_sublocation2.name
24
+
25
+ # Test by location
26
+ today = fields.Datetime.today()
27
+ future = fields.Datetime.add(
28
+ fields.Datetime.end_of(fields.Datetime.today(), "day"), days=2
29
+ )
30
+ # change date to lines in the same location
31
+ move1 = self.picking2.move_ids[0]
32
+ move1.write({"date": today})
33
+ move1_line = move1.move_line_ids[0]
34
+ move2 = self.picking2.move_ids[1]
35
+ move2.write({"date": future})
36
+ move2_line = move2.move_line_ids[0]
37
+
38
+ self.service.work.current_lines_order = "location"
39
+ move_lines = self.service._find_location_move_lines()
40
+ order_mapping = {line: i for i, line in enumerate(move_lines)}
41
+ self.assertTrue(order_mapping[move1_line] < order_mapping[move2_line])
42
+
43
+ # swap dates
44
+ move2.write({"date": today})
45
+ move1.write({"date": future})
46
+ move_lines = self.service._find_location_move_lines()
47
+ order_mapping = {line: i for i, line in enumerate(move_lines)}
48
+ self.assertTrue(order_mapping[move1_line] > order_mapping[move2_line])
49
+
50
+ # Test by priority
51
+ self.picking2.write({"priority": "0"})
52
+ (self.pickings - self.picking2).write({"priority": "1"})
53
+
54
+ self.service.work.current_lines_order = "priority"
55
+ move_lines = self.service._find_location_move_lines()
56
+ order_mapping = {line: i for i, line in enumerate(move_lines)}
57
+ # picking2 lines stay at the end as they are low priority
58
+ self.assertTrue(move1_line in move_lines[-2:])
59
+ self.assertTrue(move2_line in move_lines[-2:])
60
+ # but move1_line comes after the other
61
+ self.assertTrue(order_mapping[move1_line] > order_mapping[move2_line])
62
+
63
+ # swap dates again
64
+ move2.write({"date": future})
65
+ move1.write({"date": today})
66
+ # and increase priority
67
+ self.picking2.write({"priority": "1"})
68
+ (self.pickings - self.picking2).write({"priority": "0"})
69
+ move_lines = self.service._find_location_move_lines()
70
+ order_mapping = {line: i for i, line in enumerate(move_lines)}
71
+ self.assertEqual(order_mapping[move1_line], 0)
72
+ self.assertEqual(order_mapping[move2_line], 1)
73
+
74
+ def test_list_move_lines_order_by_location(self):
75
+ self.service.work.current_lines_order = "location"
76
+ response = self.service.dispatch("list_move_lines", params={})
77
+ move_lines = self.service._find_location_move_lines()
78
+ res = [
79
+ x["location_src"]["name"]
80
+ for x in response["data"]["select_line"]["move_lines"]
81
+ ]
82
+ self.assertEqual(res, [x.location_id.name for x in move_lines])
83
+ self.maxDiff = None
84
+ self.assert_response_select_line(
85
+ response,
86
+ self.zone_location,
87
+ self.picking1.picking_type_id,
88
+ move_lines,
89
+ )
90
+
91
+ def test_list_move_lines_order_by_priority(self):
92
+ response = self.service.dispatch("list_move_lines", params={})
93
+ move_lines = self.service._find_location_move_lines()
94
+ self.assert_response_select_line(
95
+ response,
96
+ self.zone_location,
97
+ self.picking_type,
98
+ move_lines,
99
+ )
100
+
101
+ def test_scan_source_barcode_location_not_allowed(self):
102
+ """Scan source: scanned location not allowed."""
103
+ response = self.service.dispatch(
104
+ "scan_source",
105
+ params={"barcode": self.customer_location.barcode},
106
+ )
107
+ move_lines = self.service._find_location_move_lines()
108
+ self.assert_response_select_line(
109
+ response,
110
+ self.zone_location,
111
+ self.picking_type,
112
+ move_lines,
113
+ message=self.service.msg_store.location_not_allowed(),
114
+ )
115
+
116
+ def test_scan_source_barcode_location_one_move_line(self):
117
+ """Scan source: scanned location 'Zone sub-location 1' contains only
118
+ one move line, next step 'set_line_destination' expected.
119
+ """
120
+ response = self.service.dispatch(
121
+ "scan_source",
122
+ params={"barcode": self.zone_sublocation1.barcode},
123
+ )
124
+ move_line = self.picking1.move_line_ids
125
+ self.assert_response_set_line_destination(
126
+ response,
127
+ zone_location=self.zone_location,
128
+ picking_type=self.picking_type,
129
+ move_line=move_line,
130
+ qty_done=10.0,
131
+ )
132
+
133
+ def test_scan_source_barcode_location_two_move_lines_same_product(self):
134
+ """Scan source: scanned location 'Zone sub-location 1' contains two lines.
135
+
136
+ Lines have the same product/package/lot,
137
+ they get processed one after the other,
138
+ next step 'set_line_destination' expected.
139
+ """
140
+ package = self.picking1.move_line_ids.mapped("package_id")[0]
141
+ new_picking = self._create_picking(lines=[(self.product_a, 20)])
142
+ self._fill_stock_for_moves(
143
+ new_picking.move_ids, in_package=package, location=self.zone_sublocation1
144
+ )
145
+ new_picking.action_assign()
146
+ response = self.service.dispatch(
147
+ "scan_source",
148
+ params={"barcode": self.zone_sublocation1.barcode},
149
+ )
150
+ move_line = self.picking1.move_line_ids
151
+ self.assert_response_set_line_destination(
152
+ response,
153
+ zone_location=self.zone_location,
154
+ picking_type=self.picking_type,
155
+ move_line=move_line,
156
+ qty_done=10.0,
157
+ )
158
+ # first line done
159
+ move_line.qty_done = move_line.reserved_uom_qty
160
+ # get the next one
161
+ response = self.service.dispatch(
162
+ "scan_source",
163
+ params={"barcode": self.zone_sublocation1.barcode},
164
+ )
165
+ move_line = new_picking.move_line_ids
166
+ self.assert_response_set_line_destination(
167
+ response,
168
+ zone_location=self.zone_location,
169
+ picking_type=self.picking_type,
170
+ move_line=move_line,
171
+ qty_done=10.0,
172
+ )
173
+
174
+ def test_scan_source_barcode_location_several_move_lines(self):
175
+ """Scan source: scanned location 'Zone sub-location 2' contains two
176
+ move lines, next step 'select_line' expected with the list of these
177
+ move lines.
178
+ """
179
+ response = self.service.dispatch(
180
+ "scan_source",
181
+ params={"barcode": self.zone_sublocation2.barcode},
182
+ )
183
+ move_lines = self.pickings.move_line_ids.filtered(
184
+ lambda l: l.location_id == self.zone_sublocation2
185
+ ).sorted(
186
+ self.service.search_move_line._sort_key_move_lines(self.service.lines_order)
187
+ )
188
+ self.assert_response_select_line(
189
+ response,
190
+ zone_location=self.zone_location,
191
+ picking_type=self.picking_type,
192
+ move_lines=move_lines,
193
+ message=self.service.msg_store.several_products_in_location(
194
+ self.zone_sublocation2
195
+ ),
196
+ sublocation=self.zone_sublocation2,
197
+ location_first=False,
198
+ )
199
+
200
+ def test_scan_source_barcode_package(self):
201
+ """Scan source: scanned package has one related move line,
202
+ next step 'set_line_destination' expected on it.
203
+ """
204
+ package = self.picking1.package_level_ids[0].package_id
205
+ response = self.service.dispatch(
206
+ "scan_source",
207
+ params={"barcode": package.name},
208
+ )
209
+ move_lines = self.service._find_location_move_lines(
210
+ package=package,
211
+ )
212
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
213
+ move_line = move_lines[0]
214
+ self.assert_response_set_line_destination(
215
+ response,
216
+ zone_location=self.zone_location,
217
+ picking_type=self.picking_type,
218
+ move_line=move_line,
219
+ qty_done=10.0,
220
+ )
221
+
222
+ def test_scan_source_barcode_package_not_found(self):
223
+ """Scan source: scanned package has no related move line,
224
+ next step 'select_line' expected.
225
+ """
226
+ self.free_package.location_id = self.zone_location
227
+ pack_code = self.free_package.name
228
+ response = self.service.dispatch(
229
+ "scan_source",
230
+ params={"barcode": pack_code},
231
+ )
232
+ move_lines = self.service._find_location_move_lines()
233
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
234
+ self.assert_response_select_line(
235
+ response,
236
+ zone_location=self.zone_location,
237
+ picking_type=self.picking_type,
238
+ move_lines=move_lines,
239
+ message=self.service.msg_store.package_has_no_product_to_take(pack_code),
240
+ )
241
+
242
+ def test_scan_source_barcode_package_not_exist(self):
243
+ """Scan source: scanned package that does not exist in the system
244
+ next step 'select_line' expected.
245
+ """
246
+ response = self.service.dispatch(
247
+ "scan_source",
248
+ params={"barcode": "P-Unknown"},
249
+ )
250
+ move_lines = self.service._find_location_move_lines()
251
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
252
+ self.assert_response_select_line(
253
+ response,
254
+ zone_location=self.zone_location,
255
+ picking_type=self.picking_type,
256
+ move_lines=move_lines,
257
+ message=self.service.msg_store.barcode_not_found(),
258
+ )
259
+
260
+ def test_scan_source_package_many_products(self):
261
+ """Scan source: scanned package that several product, aborting
262
+ next step 'select_line expected.
263
+ """
264
+ pack = self.picking1.package_level_ids[0].package_id
265
+ self._update_qty_in_location(pack.location_id, self.product_b, 2, pack)
266
+ response = self.service.dispatch(
267
+ "scan_source",
268
+ params={"barcode": pack.name},
269
+ )
270
+ move_lines = self.service._find_location_move_lines(
271
+ locations=self.zone_sublocation1
272
+ )
273
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
274
+ self.assert_response_select_line(
275
+ response,
276
+ zone_location=self.zone_location,
277
+ picking_type=self.picking_type,
278
+ move_lines=move_lines,
279
+ package=pack,
280
+ message=self.service.msg_store.several_products_in_package(pack),
281
+ location_first=False,
282
+ )
283
+
284
+ def test_scan_source_barcode_package_can_replace_in_line(self):
285
+ """Scan source: scanned package has no related line but can replace
286
+ next step 'select_line' expected with confirmation required set.
287
+ Scan source: 2nd time the package replace package line with new package
288
+ next step 'set_line_destination'.
289
+ """
290
+ # Add the same product same package in the same location to use as replacement
291
+ picking1b = self._create_picking(lines=[(self.product_a, 10)])
292
+ self._fill_stock_for_moves(
293
+ picking1b.move_ids, in_package=True, location=self.zone_sublocation1
294
+ )
295
+ picking1b.action_assign()
296
+ package1b = picking1b.package_level_ids[0].package_id
297
+ picking1b.action_cancel()
298
+ package1 = self.picking1.package_level_ids[0].package_id
299
+ # 1st scan
300
+ response = self.service.dispatch(
301
+ "scan_source",
302
+ params={"barcode": package1b.name},
303
+ )
304
+ move_lines = self.service._find_location_move_lines(
305
+ package=package1,
306
+ )
307
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
308
+ self.assert_response_select_line(
309
+ response,
310
+ zone_location=self.zone_location,
311
+ picking_type=self.picking_type,
312
+ move_lines=move_lines,
313
+ message=self.service.msg_store.package_different_change(),
314
+ confirmation_required=True,
315
+ )
316
+ self.assertEqual(self.picking1.package_level_ids[0].package_id, package1)
317
+ # 2nd scan
318
+ response = self.service.dispatch(
319
+ "scan_source",
320
+ params={"barcode": package1b.name, "confirmation": True},
321
+ )
322
+ self.assert_response_set_line_destination(
323
+ response,
324
+ zone_location=self.zone_location,
325
+ picking_type=self.picking_type,
326
+ move_line=move_lines[0],
327
+ message=self.service.msg_store.package_replaced_by_package(
328
+ package1, package1b
329
+ ),
330
+ )
331
+ # Check the package has been changed on the move line
332
+ self.assertEqual(self.picking1.package_level_ids[0].package_id, package1b)
333
+
334
+ def test_scan_source_barcode_product(self):
335
+ """Scan source: scanned product has one related move line,
336
+ next step 'set_line_destination' expected on it.
337
+ """
338
+ response = self.service.dispatch(
339
+ "scan_source",
340
+ params={"barcode": self.product_a.barcode},
341
+ )
342
+ move_line = self.service._find_location_move_lines(
343
+ product=self.product_a,
344
+ )
345
+ self.assert_response_set_line_destination(
346
+ response,
347
+ zone_location=self.zone_location,
348
+ picking_type=self.picking_type,
349
+ move_line=move_line,
350
+ qty_done=10.0,
351
+ )
352
+
353
+ def test_scan_source_barcode_product_not_found(self):
354
+ """Scan source: scanned product has no related move line,
355
+ next step 'select_line' expected.
356
+ """
357
+ response = self.service.dispatch(
358
+ "scan_source",
359
+ params={"barcode": self.free_product.barcode},
360
+ )
361
+ move_lines = self.service._find_location_move_lines()
362
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
363
+ self.assert_response_select_line(
364
+ response,
365
+ zone_location=self.zone_location,
366
+ picking_type=self.picking_type,
367
+ move_lines=move_lines,
368
+ message=self.service.msg_store.product_not_found_in_pickings(),
369
+ )
370
+
371
+ def test_scan_source_barcode_product_multiple_moves_different_location(self):
372
+ """Scan source: scanned product has move lines in multiple sub location.
373
+
374
+ next step : 'select_line' expected.
375
+
376
+ Then scan a location and a specific line is selected.
377
+
378
+ next step : 'set_line_destination'
379
+ """
380
+ # Using picking4 which has a product in two sublocation
381
+ response = self.service.dispatch(
382
+ "scan_source",
383
+ params={"barcode": self.product_e.barcode},
384
+ )
385
+ move_lines = self.service._find_location_move_lines(product=self.product_e)
386
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
387
+ self.assert_response_select_line(
388
+ response,
389
+ zone_location=self.zone_location,
390
+ picking_type=self.picking_type,
391
+ move_lines=move_lines,
392
+ product=self.product_e,
393
+ message=self.service.msg_store.several_move_in_different_location(),
394
+ )
395
+ response = self.service.dispatch(
396
+ "scan_source",
397
+ params={
398
+ "barcode": self.zone_sublocation3.barcode,
399
+ "product_id": self.product_e.id,
400
+ },
401
+ )
402
+ self.assertEqual(response["next_state"], "set_line_destination")
403
+ move_line = self.service._find_location_move_lines(
404
+ product=self.product_e, locations=self.zone_sublocation3
405
+ )
406
+ self.assert_response_set_line_destination(
407
+ response,
408
+ zone_location=self.zone_location,
409
+ picking_type=self.picking_type,
410
+ move_line=move_line,
411
+ qty_done=6.0,
412
+ )
413
+
414
+ def test_scan_source_barcode_location_multiple_moves_different_product(self):
415
+ """Scan source: scanned location has move lines with multiple product.
416
+
417
+ next step : 'select_line' expected.
418
+
419
+ Then scan a product and a specific line is selected.
420
+
421
+ next step : 'set_line_destination'
422
+ """
423
+ # Using picking4 which has a product in two sublocation
424
+ response = self.service.dispatch(
425
+ "scan_source",
426
+ params={"barcode": self.zone_sublocation3.barcode},
427
+ )
428
+ move_lines = self.service._find_location_move_lines(
429
+ locations=self.zone_sublocation3
430
+ )
431
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
432
+ self.assert_response_select_line(
433
+ response,
434
+ zone_location=self.zone_location,
435
+ picking_type=self.picking_type,
436
+ move_lines=move_lines,
437
+ sublocation=self.zone_sublocation3,
438
+ message=self.service.msg_store.several_products_in_location(
439
+ self.zone_sublocation3
440
+ ),
441
+ location_first=False,
442
+ )
443
+ response = self.service.dispatch(
444
+ "scan_source",
445
+ params={
446
+ "barcode": self.product_e.barcode,
447
+ "sublocation_id": self.zone_sublocation3.id,
448
+ },
449
+ )
450
+ self.assertEqual(response["next_state"], "set_line_destination")
451
+ move_line = self.service._find_location_move_lines(
452
+ product=self.product_e, locations=self.zone_sublocation3
453
+ )
454
+ self.assert_response_set_line_destination(
455
+ response,
456
+ zone_location=self.zone_location,
457
+ picking_type=self.picking_type,
458
+ move_line=move_line,
459
+ qty_done=6.0,
460
+ )
461
+
462
+ def test_scan_source_barcode_product_with_multiple_lot(self):
463
+ """Scan source: scanned product is found with mulitple lot number.
464
+
465
+ next step : 'select_line' expected.
466
+ """
467
+ # Product C has already one lot from test_zone_picking_base.py
468
+ # So lets add one more lot for that product in same location.
469
+ pick = self._create_picking(lines=[(self.product_c, 10)])
470
+ self._fill_stock_for_moves(
471
+ pick.move_ids, in_lot=True, location=self.zone_sublocation2
472
+ )
473
+ pick.action_assign()
474
+ response = self.service.dispatch(
475
+ "scan_source",
476
+ params={"barcode": self.product_c.barcode},
477
+ )
478
+ move_lines = self.service._find_location_move_lines(product=self.product_c)
479
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
480
+ self.assert_response_select_line(
481
+ response,
482
+ zone_location=self.zone_location,
483
+ picking_type=self.picking_type,
484
+ move_lines=move_lines,
485
+ product=self.product_c,
486
+ message=self.service.msg_store.several_move_with_different_lot(),
487
+ )
488
+
489
+ def test_scan_source_barcode_lot(self):
490
+ """Scan source: scanned lot has one related move line,
491
+ next step 'set_line_destination' expected on it.
492
+ """
493
+ # Product C has already one lot from test_zone_picking_base.py
494
+ # So lets add one more lot for that product in a different location.
495
+ pick = self._create_picking(lines=[(self.product_c, 10)])
496
+ self._fill_stock_for_moves(
497
+ pick.move_ids, in_lot=True, location=self.zone_sublocation1
498
+ )
499
+ pick.action_assign()
500
+ lot = self.picking2.move_line_ids.lot_id[0]
501
+ response = self.service.dispatch(
502
+ "scan_source",
503
+ params={"barcode": lot.name},
504
+ )
505
+ move_lines = self.service._find_location_move_lines(lot=lot)
506
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
507
+ move_line = move_lines[0]
508
+ self.assert_response_set_line_destination(
509
+ response,
510
+ zone_location=self.zone_location,
511
+ picking_type=self.picking_type,
512
+ move_line=move_line,
513
+ qty_done=10.0,
514
+ )
515
+
516
+ def test_scan_source_barcode_lot_in_multiple_location(self):
517
+ """Scan source: scanned lot is in multiple location
518
+ next step 'select_line' expected on it.
519
+ """
520
+ # Picking 2 has already some lot from test_zone_picking_base.py
521
+ # So lets add in the same lot the same product in another location.
522
+ lot = self.picking2.move_line_ids.lot_id[0]
523
+ picking = self._create_picking(lines=[(lot.product_id, 2)])
524
+ self._fill_stock_for_moves(
525
+ picking.move_ids, in_lot=lot, location=self.zone_sublocation3
526
+ )
527
+ picking.action_assign()
528
+ response = self.service.dispatch(
529
+ "scan_source",
530
+ params={"barcode": lot.name},
531
+ )
532
+ # FIX ME: need to filter lines only on lot scanned !!
533
+ move_lines = self.service._find_location_move_lines()
534
+ # move_lines = self.service._find_location_move_lines(lot=lot)
535
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
536
+ self.assert_response_select_line(
537
+ response,
538
+ zone_location=self.zone_location,
539
+ picking_type=self.picking_type,
540
+ move_lines=move_lines,
541
+ message=self.service.msg_store.several_move_in_different_location(),
542
+ )
543
+
544
+ def test_scan_source_barcode_lot_not_found(self):
545
+ """Scan source: scanned lot has no related move line,
546
+ next step 'select_line' expected.
547
+ """
548
+ response = self.service.dispatch(
549
+ "scan_source",
550
+ params={"barcode": self.free_lot.name},
551
+ )
552
+ move_lines = self.service._find_location_move_lines()
553
+ move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
554
+ self.assert_response_select_line(
555
+ response,
556
+ zone_location=self.zone_location,
557
+ picking_type=self.picking_type,
558
+ move_lines=move_lines,
559
+ message=self.service.msg_store.lot_not_found_in_pickings(),
560
+ )
561
+
562
+ def test_scan_source_barcode_not_found(self):
563
+ response = self.service.dispatch(
564
+ "scan_source",
565
+ params={
566
+ "zone_location_id": self.zone_location.id,
567
+ "picking_type_id": self.picking_type.id,
568
+ "barcode": "UNKNOWN",
569
+ },
570
+ )
571
+ move_lines = self.service._find_location_move_lines()
572
+ self.assert_response_select_line(
573
+ response,
574
+ zone_location=self.zone_location,
575
+ picking_type=self.picking_type,
576
+ move_lines=move_lines,
577
+ message=self.service.msg_store.barcode_not_found(),
578
+ )
579
+
580
+ def test_scan_source_multi_users(self):
581
+ """First user scans the source location 'Zone sub-location 1' containing
582
+ only one move line, then processes the next step 'set_line_destination'.
583
+
584
+ The second user scans the same source location, and should not find any line.
585
+ """
586
+ # The first user starts to process the only line available
587
+ # - scan source
588
+ response = self.service.scan_source(
589
+ self.zone_sublocation1.barcode,
590
+ )
591
+ move_line = self.picking1.move_line_ids
592
+ self.assertEqual(response["next_state"], "set_line_destination")
593
+ # - set destination
594
+ self.service.set_destination(
595
+ move_line.id,
596
+ self.free_package.name,
597
+ move_line.reserved_uom_qty,
598
+ )
599
+ self.assertEqual(move_line.shopfloor_user_id, self.env.user)
600
+ # The second user scans the same source location
601
+ env = self.env(user=self.stock_user2)
602
+ with self.work_on_services(
603
+ env=env,
604
+ menu=self.menu,
605
+ profile=self.profile,
606
+ current_zone_location=self.zone_location,
607
+ current_picking_type=self.picking_type,
608
+ ) as work:
609
+ service = work.component(usage="zone_picking")
610
+ response = service.scan_source(
611
+ self.zone_sublocation1.barcode,
612
+ )
613
+ self.assertEqual(response["next_state"], "select_line")
614
+ self.assertEqual(
615
+ response["message"],
616
+ self.service.msg_store.wrong_record(self.zone_sublocation1),
617
+ )
618
+
619
+ def test_prepare_unload_buffer_empty(self):
620
+ # unload goods
621
+ response = self.service.dispatch(
622
+ "prepare_unload",
623
+ params={},
624
+ )
625
+ # check response
626
+ move_lines = self.service._find_location_move_lines()
627
+ self.assert_response_select_line(
628
+ response,
629
+ self.zone_location,
630
+ self.picking_type,
631
+ move_lines,
632
+ )
633
+
634
+ def test_prepare_unload_buffer_one_line(self):
635
+ # scan a destination package to get something in the buffer
636
+ move_line = self.picking1.move_line_ids
637
+ response = self.service.dispatch(
638
+ "set_destination",
639
+ params={
640
+ "move_line_id": move_line.id,
641
+ "barcode": self.free_package.name,
642
+ "quantity": move_line.reserved_uom_qty,
643
+ },
644
+ )
645
+ # unload goods
646
+ response = self.service.dispatch(
647
+ "prepare_unload",
648
+ params={},
649
+ )
650
+ # check response
651
+ self.assert_response_unload_set_destination(
652
+ response,
653
+ self.zone_location,
654
+ self.picking_type,
655
+ move_line,
656
+ )
657
+
658
+ def test_prepare_unload_buffer_multi_line_same_destination(self):
659
+ # scan a destination package for some move lines
660
+ # to get several lines in the buffer (which have the same destination)
661
+ self.another_package = self.env["stock.quant.package"].create(
662
+ {"name": "ANOTHER_PACKAGE"}
663
+ )
664
+ move_lines = self.picking5.move_line_ids
665
+ self.assertEqual(move_lines.location_dest_id, self.packing_location)
666
+ for move_line, package_dest in zip(
667
+ move_lines, self.free_package | self.another_package
668
+ ):
669
+ self.service.dispatch(
670
+ "set_destination",
671
+ params={
672
+ "move_line_id": move_line.id,
673
+ "barcode": package_dest.name,
674
+ "quantity": move_line.reserved_uom_qty,
675
+ },
676
+ )
677
+ # unload goods
678
+ response = self.service.dispatch(
679
+ "prepare_unload",
680
+ params={},
681
+ )
682
+ # check response
683
+ self.assert_response_unload_all(
684
+ response,
685
+ self.zone_location,
686
+ self.picking_type,
687
+ move_lines,
688
+ )
689
+
690
+ def test_list_move_lines_empty_location(self):
691
+ response = self.service.dispatch(
692
+ "list_move_lines",
693
+ params={"order": "location"},
694
+ )
695
+ # TODO: order by location?
696
+ move_lines = self.service._find_location_move_lines()
697
+ self.assert_response_select_line(
698
+ response,
699
+ self.zone_location,
700
+ self.picking_type,
701
+ move_lines,
702
+ )
703
+ data_move_lines = response["data"]["select_line"]["move_lines"]
704
+ # Check that the move line in "Zone sub-location 1" is about to empty
705
+ # its location if we process it
706
+ data_move_line = [
707
+ m
708
+ for m in data_move_lines
709
+ if m["location_src"]["barcode"] == "ZONE_SUBLOCATION_1"
710
+ ][0]
711
+ self.assertTrue(data_move_line["location_will_be_empty"])
712
+ # Same check with the internal method
713
+ move_line = self.env["stock.move.line"].browse(data_move_line["id"])
714
+ location_src = move_line.location_id
715
+ move_line_will_empty_location = location_src.planned_qty_in_location_is_empty(
716
+ move_lines=move_line
717
+ )
718
+ self.assertTrue(move_line_will_empty_location)
719
+ # But if we check the location without giving the move line as parameter,
720
+ # knowing that this move line hasn't its 'qty_done' field filled,
721
+ # the location won't be considered empty with such pending move line
722
+ move_line_will_empty_location = location_src.planned_qty_in_location_is_empty()
723
+ self.assertFalse(move_line_will_empty_location)