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,70 @@
1
+ # Copyright 2022 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 ClusterPickingLineCommonCase
5
+
6
+
7
+ class ClusterPickingScanLineNoPrefillQtyCase(ClusterPickingLineCommonCase):
8
+ """Tests covering the /scan_line endpoint
9
+
10
+ With the no prefill quantity option set
11
+
12
+ """
13
+
14
+ @classmethod
15
+ def _enable_no_prefill(cls):
16
+ cls.menu.sudo().no_prefill_qty = True
17
+ cls.picking = cls.batch.picking_ids
18
+ cls.line = cls.picking.move_line_ids
19
+ cls.line.reserved_uom_qty = 3
20
+
21
+ def _assert_qty_done(self, line, scanned, expected_qty_done):
22
+ batch = line.picking_id.batch_id
23
+ response = self.service.dispatch(
24
+ "scan_line",
25
+ params={
26
+ "picking_batch_id": batch.id,
27
+ "move_line_id": line.id,
28
+ "barcode": scanned,
29
+ },
30
+ )
31
+ qty_done = response["data"]["scan_destination"]["qty_done"]
32
+ self.assertEqual(qty_done, expected_qty_done)
33
+
34
+ def test_scan_line_package_no_prefill_set(self):
35
+ """Check scanning a package when no_prefill_qty is enabled."""
36
+ self._simulate_batch_selected(self.batch, in_package=True)
37
+ self._enable_no_prefill()
38
+ self._assert_qty_done(self.line, self.line.package_id.name, 0.0)
39
+
40
+ def test_scan_line_packaging_no_prefill_set(self):
41
+ """Check scanning a packaging when no_prefill_qty is enabled."""
42
+ self._simulate_batch_selected(self.batch, in_package=False)
43
+ self._enable_no_prefill()
44
+ packaging = self.env.ref(
45
+ "stock_storage_type.product_product_9_packaging_4_cardbox"
46
+ )
47
+ packaging.sudo().write(
48
+ {"product_id": self.line.product_id.id, "barcode": "cute-pack"}
49
+ )
50
+ # The quantity of the packaging is incremented
51
+ self._assert_qty_done(self.line, packaging.barcode, packaging.qty)
52
+
53
+ def test_scan_line_product_no_prefill_set(self):
54
+ """Check qty done when product is scanned and no_prefill_qty is enabled"""
55
+ self._simulate_batch_selected(self.batch)
56
+ self._enable_no_prefill()
57
+ self._assert_qty_done(self.line, self.line.product_id.barcode, 1.0)
58
+
59
+ def test_scan_line_lot_no_prefill_set(self):
60
+ """Check qty done when lot is scanned and no_prefill_qty is enabled"""
61
+ self.product_a.tracking = "lot"
62
+ self._simulate_batch_selected(self.batch, in_lot=True)
63
+ self._enable_no_prefill()
64
+ self._assert_qty_done(self.line, self.line.lot_id.name, 1.0)
65
+
66
+ def test_scan_line_location_no_prefill_set(self):
67
+ """Check qty done when location is scanned and no_prefill_qty is enabled"""
68
+ self._simulate_batch_selected(self.batch, in_package=True)
69
+ self._enable_no_prefill()
70
+ self._assert_qty_done(self.line, self.line.location_id.barcode, 0)
@@ -0,0 +1,387 @@
1
+ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+
4
+ from odoo import fields
5
+
6
+ from .test_cluster_picking_base import ClusterPickingCommonCase
7
+
8
+ # pylint: disable=missing-return
9
+
10
+
11
+ class ClusterPickingSelectionCase(ClusterPickingCommonCase):
12
+ """Tests covering the selection of picking batches
13
+
14
+ Endpoints:
15
+
16
+ * /cluster_picking/find_batch
17
+ * /cluster_picking/list_batch
18
+ * /cluster_picking/select
19
+ * /cluster_picking/unassign
20
+
21
+ These endpoints interact with a list of picking batches.
22
+ The other endpoints that interact with a single batch (after selection)
23
+ are handled in other classes.
24
+ """
25
+
26
+ @classmethod
27
+ def setUpClassBaseData(cls, *args, **kwargs):
28
+ super().setUpClassBaseData(*args, **kwargs)
29
+ # drop base demo data and create our own batches to work with
30
+ old_batchs = cls.env["stock.picking.batch"].search([])
31
+ old_batchs.write({"state": "draft"})
32
+ old_batchs.unlink()
33
+ cls.batch1 = cls._create_picking_batch(
34
+ [[cls.BatchProduct(product=cls.product_a, quantity=3)]]
35
+ )
36
+ cls.batch2 = cls._create_picking_batch(
37
+ [[cls.BatchProduct(product=cls.product_a, quantity=3)]]
38
+ )
39
+ cls.batch3 = cls._create_picking_batch(
40
+ [[cls.BatchProduct(product=cls.product_a, quantity=3)]]
41
+ )
42
+ cls.batch4 = cls._create_picking_batch(
43
+ [[cls.BatchProduct(product=cls.product_a, quantity=3)]]
44
+ )
45
+
46
+ def _add_stock_and_assign_pickings_for_batches(self, batches):
47
+ pickings = batches.mapped("picking_ids")
48
+ self._fill_stock_for_moves(pickings.mapped("move_ids"))
49
+ pickings.action_assign()
50
+
51
+ def test_find_batch_in_progress_current_user(self):
52
+ """Find an in-progress batch assigned to the current user"""
53
+ # Simulate the client asking a batch by clicking on "get work"
54
+ self._add_stock_and_assign_pickings_for_batches(
55
+ self.batch1 | self.batch2 | self.batch3
56
+ )
57
+ self.batch3.user_id = self.env.uid
58
+ self.batch3.action_confirm() # set to in progress
59
+ response = self.service.dispatch("find_batch")
60
+
61
+ # we expect to find batch 3 as it's assigned to the current
62
+ # user and in progress (first priority)
63
+ data = self.data.picking_batch(self.batch3, with_pickings=True)
64
+ self.assert_response(
65
+ response,
66
+ next_state="confirm_start",
67
+ data=data,
68
+ )
69
+
70
+ def test_find_batch_assigned(self):
71
+ """Find a draft batch assigned to the current user"""
72
+ # batches must have all their pickings available to be selected
73
+ self._add_stock_and_assign_pickings_for_batches(
74
+ self.batch1 | self.batch2 | self.batch3
75
+ )
76
+ # batch2 in draft but assigned to the current user should be
77
+ # selected before the others
78
+ self.batch2.user_id = self.env.uid
79
+ response = self.service.dispatch("find_batch")
80
+
81
+ # The endpoint starts the batch
82
+ self.assertEqual(self.batch2.state, "in_progress")
83
+
84
+ # we expect to find batch 2 as it's assigned to the current user
85
+ data = self.data.picking_batch(self.batch2, with_pickings=True)
86
+ self.assert_response(
87
+ response,
88
+ next_state="confirm_start",
89
+ data=data,
90
+ )
91
+
92
+ def test_find_batch_unassigned_draft(self):
93
+ """Find a draft batch"""
94
+ # batches must have all their pickings available to be selected
95
+ self._add_stock_and_assign_pickings_for_batches(self.batch2 | self.batch3)
96
+ # batch1 has not all pickings available, so the first draft
97
+ # is batch2, should be selected
98
+ response = self.service.dispatch("find_batch")
99
+
100
+ # The endpoint starts the batch and assign it to self
101
+ self.assertEqual(self.batch2.user_id, self.env.user)
102
+ self.assertEqual(self.batch2.state, "in_progress")
103
+
104
+ # we expect to find batch 2 as it's the first one with all pickings
105
+ # available
106
+ data = self.data.picking_batch(self.batch2, with_pickings=True)
107
+ self.assert_response(
108
+ response,
109
+ next_state="confirm_start",
110
+ data=data,
111
+ )
112
+
113
+ def test_find_batch_not_found(self):
114
+ """No batch to work on"""
115
+ # No batch match the rules to work on them, because
116
+ # their pickings are not available
117
+ response = self.service.dispatch("find_batch")
118
+ self.assert_response(
119
+ response,
120
+ next_state="start",
121
+ message={
122
+ "message_type": "info",
123
+ "body": "No more work to do, please create a new batch transfer",
124
+ },
125
+ )
126
+
127
+ def test_list_batch(self):
128
+ """List all available batches"""
129
+ # batches must have all their pickings available to be selected
130
+ self._add_stock_and_assign_pickings_for_batches(
131
+ self.batch1 | self.batch2 | self.batch3
132
+ )
133
+ self.batch1.write({"state": "in_progress", "user_id": self.env.uid})
134
+ self.batch2.write(
135
+ {"state": "in_progress", "user_id": self.env.ref("base.user_demo").id}
136
+ )
137
+ self.batch3.write({"state": "draft", "user_id": False})
138
+
139
+ self.assertEqual(
140
+ self.env["stock.picking.batch"].search([]),
141
+ self.batch1 + self.batch2 + self.batch3 + self.batch4,
142
+ )
143
+ # Simulate the client asking the list of batches
144
+ response = self.service.dispatch("list_batch")
145
+ self.assert_response(
146
+ response,
147
+ next_state="manual_selection",
148
+ data={
149
+ "size": 2,
150
+ "records": self.data.picking_batches(self.batch1 + self.batch3),
151
+ },
152
+ )
153
+
154
+ def test_select_in_progress_assigned(self):
155
+ """Select an in-progress batch assigned to the current user"""
156
+ self._add_stock_and_assign_pickings_for_batches(self.batch1)
157
+ self.batch1.write({"state": "in_progress", "user_id": self.env.uid})
158
+ # Simulate the client selecting the batch in a list
159
+ response = self.service.dispatch(
160
+ "select", params={"picking_batch_id": self.batch1.id}
161
+ )
162
+ data = self.data.picking_batch(self.batch1)
163
+ # we don't care in these tests, 'find_batch' tests them already
164
+ data["pickings"] = self.ANY
165
+ self.assert_response(
166
+ response,
167
+ next_state="confirm_start",
168
+ data=data,
169
+ )
170
+
171
+ def test_select_draft_assigned(self):
172
+ """Select a draft batch assigned to the current user"""
173
+ self._add_stock_and_assign_pickings_for_batches(self.batch1)
174
+ self.batch1.write({"user_id": self.env.uid})
175
+ # Simulate the client selecting the batch in a list
176
+ response = self.service.dispatch(
177
+ "select", params={"picking_batch_id": self.batch1.id}
178
+ )
179
+ # The endpoint starts the batch and assign it to self
180
+ self.assertEqual(self.batch1.user_id, self.env.user)
181
+ self.assertEqual(self.batch1.state, "in_progress")
182
+ data = self.data.picking_batch(self.batch1)
183
+ # we don't care in these tests, 'find_batch' tests them already
184
+ data["pickings"] = self.ANY
185
+ self.assert_response(
186
+ response,
187
+ next_state="confirm_start",
188
+ data=data,
189
+ )
190
+
191
+ def test_select_draft_unassigned(self):
192
+ """Select a draft batch not assigned to a user"""
193
+ self._add_stock_and_assign_pickings_for_batches(self.batch1)
194
+ # Simulate the client selecting the batch in a list
195
+ response = self.service.dispatch(
196
+ "select", params={"picking_batch_id": self.batch1.id}
197
+ )
198
+ # The endpoint starts the batch and assign it to self
199
+ self.assertEqual(self.batch1.user_id, self.env.user)
200
+ self.assertEqual(self.batch1.state, "in_progress")
201
+ data = self.data.picking_batch(self.batch1)
202
+ # we don't care in these tests, 'find_batch' tests them already
203
+ data["pickings"] = self.ANY
204
+ self.assert_response(
205
+ response,
206
+ next_state="confirm_start",
207
+ data=data,
208
+ )
209
+
210
+ def test_select_not_exists(self):
211
+ """Select a draft that does not exist"""
212
+ batch_id = self.batch1.id
213
+ self.batch1.state = "draft"
214
+ self.batch1.unlink()
215
+ # Simulate the client selecting the batch in a list
216
+ response = self.service.dispatch(
217
+ "select", params={"picking_batch_id": batch_id}
218
+ )
219
+ self.assert_response(
220
+ response,
221
+ next_state="manual_selection",
222
+ message={
223
+ "message_type": "warning",
224
+ "body": "This batch cannot be selected.",
225
+ },
226
+ data={"size": 0, "records": []},
227
+ )
228
+
229
+ def test_select_already_assigned(self):
230
+ """Select a draft that does not exist"""
231
+ self._add_stock_and_assign_pickings_for_batches(self.batch1)
232
+ self.batch1.write(
233
+ {"state": "in_progress", "user_id": self.env.ref("base.user_demo").id}
234
+ )
235
+ # Simulate the client selecting the batch in a list
236
+ response = self.service.dispatch(
237
+ "select", params={"picking_batch_id": self.batch1.id}
238
+ )
239
+ self.assert_response(
240
+ response,
241
+ next_state="manual_selection",
242
+ message={
243
+ "message_type": "warning",
244
+ "body": "This batch cannot be selected.",
245
+ },
246
+ data={"size": 0, "records": []},
247
+ )
248
+
249
+ def test_unassign_batch(self):
250
+ """User cancels after selecting a batch, unassign it"""
251
+ self._simulate_batch_selected(self.batch1)
252
+ # Simulate the client selecting the batch in a list
253
+ response = self.service.dispatch(
254
+ "unassign", params={"picking_batch_id": self.batch1.id}
255
+ )
256
+ self.assertEqual(self.batch1.state, "draft")
257
+ self.assertFalse(self.batch1.user_id)
258
+ self.assert_response(response, next_state="start")
259
+
260
+ def test_unassign_batch_not_exists(self):
261
+ """User cancels after selecting a batch deleted meanwhile"""
262
+ batch_id = self.batch1.id
263
+ self.batch1.state = "draft"
264
+ self.batch1.unlink()
265
+ # Simulate the client selecting the batch in a list
266
+ response = self.service.dispatch(
267
+ "unassign", params={"picking_batch_id": batch_id}
268
+ )
269
+ self.assert_response(response, next_state="start")
270
+
271
+
272
+ class ClusterPickingSelectedCase(ClusterPickingCommonCase):
273
+ """Tests covering endpoints working on a single picking batch
274
+
275
+ After a batch has been selected, by the tests covered in
276
+ ``ClusterPickingSelectionCase``.
277
+ """
278
+
279
+ @classmethod
280
+ def setUpClassBaseData(cls, *args, **kwargs):
281
+ super().setUpClassBaseData(*args, **kwargs)
282
+ cls.batch = cls._create_picking_batch(
283
+ [[cls.BatchProduct(product=cls.product_a, quantity=1)]]
284
+ )
285
+ cls._simulate_batch_selected(cls.batch, in_package=True)
286
+ cls.batch2 = cls._create_picking_batch(
287
+ [
288
+ [cls.BatchProduct(product=cls.product_a, quantity=1)],
289
+ [cls.BatchProduct(product=cls.product_a, quantity=1)],
290
+ [cls.BatchProduct(product=cls.product_b, quantity=1)],
291
+ [cls.BatchProduct(product=cls.product_b, quantity=1)],
292
+ ]
293
+ )
294
+ cls._simulate_batch_selected(cls.batch2, in_package=True)
295
+
296
+ def test_lines_order(self):
297
+ batch = self.batch2
298
+ picking1 = batch.picking_ids[0]
299
+ today = fields.Datetime.today()
300
+ future = fields.Datetime.add(
301
+ fields.Datetime.end_of(fields.Datetime.today(), "day"), days=2
302
+ )
303
+ # Change dates
304
+ move1 = picking1.move_ids[0]
305
+ move1_line = move1.move_line_ids[0]
306
+ move1.write({"date": today})
307
+ (batch.picking_ids.move_ids - move1).write({"date": future})
308
+
309
+ move_lines = self.service._lines_for_picking_batch(batch)
310
+ order_mapping = {line: i for i, line in enumerate(move_lines)}
311
+
312
+ # Today line comes first
313
+ self.assertEqual(order_mapping[move1_line], 0)
314
+ # swap dates
315
+ move1.write({"date": future})
316
+ (batch.picking_ids.move_ids - move1).write({"date": today})
317
+
318
+ move_lines = self.service._lines_for_picking_batch(batch)
319
+ order_mapping = {line: i for i, line in enumerate(move_lines)}
320
+ self.assertEqual(order_mapping[move1_line], len(move_lines) - 1)
321
+ # TODO: we should test all the combo of keys affecting sorting.
322
+
323
+ def test_confirm_start_ok(self):
324
+ """User confirms she starts the selected picking batch (happy path)"""
325
+ # batch1 was already selected, we only need to confirm the selection
326
+ batch = self.batch
327
+ self.assertEqual(batch.state, "in_progress")
328
+ picking = batch.picking_ids[0]
329
+ first_move_line = picking.move_line_ids[0]
330
+ self.assertTrue(first_move_line)
331
+ # A package exists on the move line, because the quant created
332
+ # by ``_simulate_batch_selected`` has a package.
333
+ package = first_move_line.package_id
334
+ self.assertTrue(package)
335
+
336
+ response = self.service.dispatch(
337
+ "confirm_start", params={"picking_batch_id": self.batch.id}
338
+ )
339
+ data = self.data.move_line(first_move_line)
340
+ data["package_dest"] = None
341
+ data["picking"] = self.data.picking(picking)
342
+ data["batch"] = self.data.picking_batch(batch)
343
+ data["scan_location_or_pack_first"] = False
344
+ self.assert_response(
345
+ response,
346
+ data=data,
347
+ next_state="start_line",
348
+ )
349
+
350
+ def test_confirm_start_not_exists(self):
351
+ """User confirms she starts but batch has been deleted meanwhile"""
352
+ batch_id = self.batch.id
353
+ self.batch.state = "draft"
354
+ self.batch.unlink()
355
+ response = self.service.dispatch(
356
+ "confirm_start", params={"picking_batch_id": batch_id}
357
+ )
358
+ self.assert_response(
359
+ response,
360
+ message={
361
+ "message_type": "error",
362
+ "body": "The record you were working on does not exist anymore.",
363
+ },
364
+ next_state="start",
365
+ )
366
+
367
+ def test_confirm_start_all_is_done(self):
368
+ """User confirms start but all lines are already done"""
369
+ # we want to jump to the start because there are no lines
370
+ # to process anymore, but we want to set pickings and
371
+ # picking batch to done if not done yet (because the process
372
+ # was interrupted for instance)
373
+ self._set_dest_package_and_done(
374
+ self.batch.mapped("picking_ids.move_line_ids"),
375
+ self.env["stock.quant.package"].create({}),
376
+ )
377
+ self.batch.action_done()
378
+ response = self.service.dispatch(
379
+ "confirm_start", params={"picking_batch_id": self.batch.id}
380
+ )
381
+ self.assert_response(
382
+ response,
383
+ next_state="start",
384
+ message={"body": "Batch Transfer complete", "message_type": "success"},
385
+ )
386
+
387
+ # TODO: add a test for lines sorting
@@ -0,0 +1,90 @@
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 ClusterPickingSkipLineCase(ClusterPickingCommonCase):
10
+ """Tests covering the /skip_line endpoint"""
11
+
12
+ @classmethod
13
+ def setUpClassBaseData(cls, *args, **kwargs):
14
+ super().setUpClassBaseData(*args, **kwargs)
15
+ # quants already existing are from demo data
16
+ cls.env["stock.quant"].sudo().search(
17
+ [("location_id", "=", cls.stock_location.id)]
18
+ ).unlink()
19
+ cls.batch = cls._create_picking_batch(
20
+ [
21
+ [
22
+ cls.BatchProduct(product=cls.product_a, quantity=10),
23
+ cls.BatchProduct(product=cls.product_b, quantity=20),
24
+ ],
25
+ [
26
+ cls.BatchProduct(product=cls.product_a, quantity=30),
27
+ cls.BatchProduct(product=cls.product_b, quantity=40),
28
+ ],
29
+ ]
30
+ )
31
+
32
+ def _skip_line(self, line, next_line=None):
33
+ response = self.service.dispatch(
34
+ "skip_line",
35
+ params={"picking_batch_id": self.batch.id, "move_line_id": line.id},
36
+ )
37
+ if next_line:
38
+ self.assert_response(
39
+ response, next_state="start_line", data=self._line_data(next_line)
40
+ )
41
+ return response
42
+
43
+ def test_skip_line(self):
44
+ # put one picking in another location
45
+ self.batch.picking_ids[1].location_id = self.shelf1
46
+ self.batch.picking_ids[1].move_ids.location_id = self.shelf1
47
+ # select batch
48
+ self._simulate_batch_selected(self.batch, in_package=True)
49
+
50
+ # enforce names to have reliable sorting
51
+ self.stock_location.sudo().name = "LOC2"
52
+ self.shelf1.sudo().name = "LOC1"
53
+ all_lines = self.batch.picking_ids.move_line_ids
54
+ loc1_lines = all_lines.filtered(lambda line: (line.location_id == self.shelf1))
55
+ loc2_lines = all_lines.filtered(
56
+ lambda line: (line.location_id == self.stock_location)
57
+ )
58
+ # no line postponed yet
59
+ self.assertEqual(
60
+ all_lines.mapped("shopfloor_postponed"), [False, False, False, False]
61
+ )
62
+
63
+ # skip line from loc 1
64
+ previous_priority = loc1_lines[0].shopfloor_priority
65
+ self._skip_line(loc1_lines[0], loc1_lines[1])
66
+ self.assertEqual(loc1_lines[0].shopfloor_priority, previous_priority + 1)
67
+ loc1_lines.invalidate_recordset(["shopfloor_postponed"])
68
+ self.assertTrue(loc1_lines[0].shopfloor_postponed)
69
+
70
+ # 2nd line, next is 1st from 2nd location
71
+ self.assertFalse(loc1_lines[1].shopfloor_postponed)
72
+ self._skip_line(loc1_lines[1], loc2_lines[0])
73
+ # Priority is now the current max + 1
74
+ self.assertEqual(
75
+ loc1_lines[1].shopfloor_priority, loc1_lines[0].shopfloor_priority + 1
76
+ )
77
+ loc1_lines.invalidate_recordset(["shopfloor_postponed"])
78
+ self.assertTrue(loc1_lines[1].shopfloor_postponed)
79
+
80
+ # 3rd line, next is 4th
81
+ self.assertFalse(loc2_lines[0].shopfloor_postponed)
82
+ self._skip_line(loc2_lines[0], loc2_lines[1])
83
+ self.assertEqual(
84
+ loc2_lines[0].shopfloor_priority, loc1_lines[1].shopfloor_priority + 1
85
+ )
86
+ loc1_lines.invalidate_recordset(["shopfloor_postponed"])
87
+ self.assertTrue(loc2_lines[0].shopfloor_postponed)
88
+
89
+
90
+ # TODO tests for transitions to next line / no next lines, ...