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,364 @@
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 ClusterPickingStockIssue(ClusterPickingCommonCase):
10
+ """Tests covering the /stock_issue endpoint"""
11
+
12
+ @classmethod
13
+ def setUpClassBaseData(cls, *args, **kwargs):
14
+ super().setUpClassBaseData(*args, **kwargs)
15
+ # quants already existing are from demo data
16
+ loc_ids = (cls.stock_location.id, cls.shelf1.id, cls.shelf2.id)
17
+ cls.env["stock.quant"].sudo().search([("location_id", "in", loc_ids)]).unlink()
18
+ cls.batch = cls._create_picking_batch(
19
+ [
20
+ [cls.BatchProduct(product=cls.product_a, quantity=10)],
21
+ [cls.BatchProduct(product=cls.product_a, quantity=5)],
22
+ [cls.BatchProduct(product=cls.product_a, quantity=20)],
23
+ [cls.BatchProduct(product=cls.product_a, quantity=10)],
24
+ [cls.BatchProduct(product=cls.product_a, quantity=7)],
25
+ ]
26
+ )
27
+
28
+ cls.moves = cls.batch.picking_ids.move_ids.sorted("id")
29
+ cls.move1, cls.move2, cls.move3, cls.move4, cls.move5 = cls.moves
30
+ cls.batch_other = cls._create_picking_batch(
31
+ [[cls.BatchProduct(product=cls.product_a, quantity=30)]]
32
+ )
33
+ cls.dest_package = cls.env["stock.quant.package"].create({})
34
+
35
+ def _stock_issue(self, line, next_line_func=None):
36
+ batch = line.picking_id.batch_id
37
+ response = self.service.dispatch(
38
+ "stock_issue",
39
+ params={"picking_batch_id": batch.id, "move_line_id": line.id},
40
+ )
41
+ # use a function/lambda to delay the read of the next line,
42
+ # when calling _stock_issue(), the move_line may not exist and
43
+ # be created during the call to the stock_issue service
44
+ if next_line_func:
45
+ self.assert_response(
46
+ response,
47
+ next_state="start_line",
48
+ data=self._line_data(next_line_func()),
49
+ )
50
+ else:
51
+ self.assert_response(
52
+ response,
53
+ next_state="unload_all",
54
+ data=self._data_for_batch(self.batch, self.packing_location),
55
+ )
56
+ return response
57
+
58
+ def assert_location_qty_and_reserved(
59
+ self, location, expected_reserved_qty, lot=None
60
+ ):
61
+ quant_domain = [("location_id", "=", location.id)]
62
+ if lot:
63
+ quant_domain += [("lot_id", "=", lot.id)]
64
+ location_quants = self.env["stock.quant"].search(quant_domain)
65
+ self.assertEqual(sum(location_quants.mapped("quantity")), expected_reserved_qty)
66
+ self.assertEqual(
67
+ sum(location_quants.mapped("reserved_quantity")), expected_reserved_qty
68
+ )
69
+
70
+ def assert_stock_issue_inventories(
71
+ self, location, product, remaining_qty, lot=None
72
+ ):
73
+ domain = [
74
+ ("location_id", "=", location.id),
75
+ ("product_id", "=", product.id),
76
+ ]
77
+ if lot:
78
+ domain.append(("lot_id", "=", lot.id))
79
+ quant = self.env["stock.quant"].search(domain, order="id desc", limit=1)
80
+ self.assertEqual(quant.quantity, remaining_qty)
81
+ self.assertFalse(quant.inventory_quantity_set)
82
+
83
+ def test_stock_issue_with_other_batch(self):
84
+ self._update_qty_in_location(self.shelf1, self.product_a, 25)
85
+ # The other batch will reserve 25 in shelf 1, now empty
86
+ self.batch_other.picking_ids.action_assign()
87
+
88
+ self._update_qty_in_location(self.shelf2, self.product_a, 100)
89
+ # and then, the other batch reserves 5 in shelf 2.
90
+ # We'll want to check that even if on our batch we have a stock issue,
91
+ # we never change anything in the batch of another operator.
92
+ self._simulate_batch_selected(self.batch_other, fill_stock=False)
93
+ self.assertEqual(
94
+ set(self.batch_other.picking_ids.mapped("state")), {"assigned"}
95
+ )
96
+
97
+ # At this point, we have a remaining quantity of 0 in shelf1
98
+ # and 95 in shelf2.
99
+
100
+ # all the moves of our batch should be reserved as we have enough
101
+ # stock
102
+ self._simulate_batch_selected(self.batch, fill_stock=False)
103
+ self.assertEqual(set(self.batch.picking_ids.mapped("state")), {"assigned"})
104
+
105
+ # the operator could pick the 2 first lines of the batch
106
+ self._set_dest_package_and_done(self.move1.move_line_ids, self.dest_package)
107
+ self._set_dest_package_and_done(self.move2.move_line_ids, self.dest_package)
108
+
109
+ # on the third move, the operator can't pick anymore in shelf1
110
+ # because there is nothing inside, they declare a stock issue
111
+ self._stock_issue(self.move3.move_line_ids)
112
+
113
+ self.assertRecordValues(
114
+ self.moves,
115
+ [
116
+ {"state": "assigned"},
117
+ {"state": "assigned"},
118
+ {"state": "confirmed"},
119
+ {"state": "confirmed"},
120
+ {"state": "confirmed"},
121
+ ],
122
+ )
123
+ expected_reserved_qty = (
124
+ self.move1.product_uom_qty
125
+ + self.move2.product_uom_qty
126
+ + sum(
127
+ self.batch_other.picking_ids.move_line_ids.filtered(
128
+ lambda l: l.location_id == self.shelf2
129
+ ).mapped("reserved_uom_qty")
130
+ )
131
+ )
132
+ # we should have a quant with 20 quantity and 20 reserved
133
+ # (5 for the other batch and 15 qty_done in this batch)
134
+ self.assert_location_qty_and_reserved(self.shelf2, expected_reserved_qty)
135
+ self.assert_stock_issue_inventories(
136
+ self.shelf2,
137
+ self.move3.product_id,
138
+ expected_reserved_qty,
139
+ )
140
+
141
+ def test_stock_issue_several_move_lines(self):
142
+ self._update_qty_in_location(self.shelf1, self.product_a, 20)
143
+ # ensure these moves are reserved in shelf1
144
+ self.move1._action_assign()
145
+ self.move2._action_assign()
146
+
147
+ self._update_qty_in_location(self.shelf2, self.product_a, 100)
148
+ # reserve move3 first to ensure this one is reserved in both
149
+ # shelf1 and shelf2
150
+ self.move3._action_assign()
151
+
152
+ # all the remaining moves will be reserved in shelf2
153
+ self._simulate_batch_selected(self.batch, fill_stock=False)
154
+ self.assertEqual(set(self.batch.picking_ids.mapped("state")), {"assigned"})
155
+ # The moves of our batch are reserved as:
156
+ self.assertEqual(self.move1.move_line_ids.location_id, self.shelf1)
157
+ self.assertEqual(self.move2.move_line_ids.location_id, self.shelf1)
158
+ self.assertEqual(
159
+ self.move3.move_line_ids.mapped("location_id"), self.shelf1 | self.shelf2
160
+ )
161
+ self.assertEqual(self.move4.move_line_ids.location_id, self.shelf2)
162
+ self.assertEqual(self.move5.move_line_ids.location_id, self.shelf2)
163
+
164
+ line_shelf1 = self.move3.move_line_ids.filtered(
165
+ lambda l: l.location_id == self.shelf1
166
+ )
167
+ line_shelf2 = self.move3.move_line_ids.filtered(
168
+ lambda l: l.location_id == self.shelf2
169
+ )
170
+
171
+ # pick the first 2 moves
172
+ self._set_dest_package_and_done(self.move1.move_line_ids, self.dest_package)
173
+ self._set_dest_package_and_done(self.move2.move_line_ids, self.dest_package)
174
+ # the operator could pick the first part of move3 in shelf1
175
+ self._set_dest_package_and_done(line_shelf1, self.dest_package)
176
+
177
+ # on the third move, the operator can't pick anymore in shelf1
178
+ # because there is nothing inside, they declare a stock issue
179
+ self._stock_issue(line_shelf2)
180
+
181
+ self.assertRecordValues(
182
+ self.moves,
183
+ [
184
+ # move 1 and 2 aren't touched: they are in another location
185
+ {"state": "assigned"},
186
+ {"state": "assigned"},
187
+ {"state": "partially_available"},
188
+ {"state": "confirmed"},
189
+ {"state": "confirmed"},
190
+ ],
191
+ )
192
+ self.assertRecordValues(
193
+ # check that the other move line of the move was not altered
194
+ line_shelf1,
195
+ [
196
+ {
197
+ "location_id": self.shelf1.id,
198
+ "qty_done": 5.0,
199
+ "result_package_id": self.dest_package.id,
200
+ }
201
+ ],
202
+ )
203
+ self.assertFalse(line_shelf2.exists())
204
+ # the quantity in shelf1 should be the original one since we didn't have
205
+ # a stock issue here
206
+ self.assert_location_qty_and_reserved(self.shelf1, 20)
207
+ # since we declared the stock issue without picking anything, its
208
+ # quantity should be zero
209
+ self.assert_location_qty_and_reserved(self.shelf2, 0)
210
+ self.assert_stock_issue_inventories(self.shelf2, self.move3.product_id, 0)
211
+
212
+ def test_stock_issue_lot(self):
213
+ lot_a = self.env["stock.lot"].create(
214
+ {"product_id": self.product_a.id, "company_id": self.env.company.id}
215
+ )
216
+ lot_b = self.env["stock.lot"].create(
217
+ {"product_id": self.product_a.id, "company_id": self.env.company.id}
218
+ )
219
+ self._update_qty_in_location(
220
+ self.shelf2,
221
+ self.product_a,
222
+ self.move1.product_uom_qty + self.move5.product_uom_qty,
223
+ lot=lot_a,
224
+ )
225
+ # ensure that move 1 and 5 take lot_a (10 + 7 units), so all of them
226
+ self.move1._action_assign()
227
+ self.move5._action_assign()
228
+ # add stock for the rest of the moves
229
+ self._update_qty_in_location(self.shelf2, self.product_a, 100, lot=lot_b)
230
+ # reserve the remaining moves
231
+ self._simulate_batch_selected(self.batch, fill_stock=False)
232
+ self.assertEqual(set(self.batch.picking_ids.mapped("state")), {"assigned"})
233
+
234
+ # the operator could pick the 3 first lines of the batch
235
+ # move move1 with lot a
236
+ self._set_dest_package_and_done(self.move1.move_line_ids, self.dest_package)
237
+ # move move2 with lot b
238
+ self._set_dest_package_and_done(self.move2.move_line_ids, self.dest_package)
239
+
240
+ # on the third move, the operator can't pick anymore in the location,
241
+ # because there is nothing inside, they declare a stock issue
242
+ self._stock_issue(
243
+ self.move3.move_line_ids, next_line_func=lambda: self.move5.move_line_ids
244
+ )
245
+
246
+ self.assertRecordValues(
247
+ self.moves,
248
+ [
249
+ # still reserved because using lot a
250
+ {"state": "assigned"},
251
+ # still reserved because qty_done > 0
252
+ {"state": "assigned"},
253
+ # unreserved by the stock issue
254
+ {"state": "confirmed"},
255
+ # collaterally unreserved by the stock issue (same lot as the
256
+ # stock issue)
257
+ {"state": "confirmed"},
258
+ # still reserved because using lot a
259
+ {"state": "assigned"},
260
+ ],
261
+ )
262
+ # check the qty including lot a and lot b
263
+ total_reserved_qty = (
264
+ self.move1.product_uom_qty
265
+ + self.move2.product_uom_qty
266
+ + self.move5.product_uom_qty
267
+ )
268
+ self.assert_location_qty_and_reserved(self.shelf2, total_reserved_qty)
269
+ # this is the only product reserved for lot_b
270
+ expected_reserved_qty = self.move2.product_uom_qty
271
+ self.assert_location_qty_and_reserved(
272
+ self.shelf2, expected_reserved_qty, lot=lot_b
273
+ )
274
+ self.assert_stock_issue_inventories(
275
+ self.shelf2,
276
+ self.move3.product_id,
277
+ expected_reserved_qty,
278
+ lot=lot_b,
279
+ )
280
+
281
+ def test_stock_issue_reserve_elsewhere(self):
282
+ self._update_qty_in_location(self.shelf1, self.product_a, 100)
283
+ self._simulate_batch_selected(self.batch, fill_stock=False)
284
+ # now, everything is reserved in shelf1 as we had enough stock
285
+ self.assertEqual(set(self.batch.picking_ids.mapped("state")), {"assigned"})
286
+
287
+ # put stock in shelf2, so we can test the outcome: goods should be
288
+ # reserved in shelf2 after a stock issue
289
+ self._update_qty_in_location(self.shelf2, self.product_a, 100)
290
+
291
+ # the operator picks the first line
292
+ self._set_dest_package_and_done(self.move1.move_line_ids, self.dest_package)
293
+
294
+ # and has a stock issue on the second line
295
+ # because there is nothing inside, they declare a stock issue
296
+ self._stock_issue(
297
+ self.move2.move_line_ids, next_line_func=lambda: self.move2.move_line_ids
298
+ )
299
+
300
+ # the inventory should have been done for shelf1, and all the remaining
301
+ # moves after move1 (already picked) should have been reserved in
302
+ # shelf2
303
+ self.assertEqual(set(self.batch.picking_ids.mapped("state")), {"assigned"})
304
+ self.assertEqual(self.move1.move_line_ids.location_id, self.shelf1)
305
+ # all the following moves have been reserved in shelf2 as we still have
306
+ # stock there
307
+ self.assertEqual(
308
+ (self.move2 | self.move3 | self.move4 | self.move5).mapped(
309
+ "move_line_ids.location_id"
310
+ ),
311
+ self.shelf2,
312
+ )
313
+
314
+ def test_stock_issue_similar_move_with_picked_line(self):
315
+ """Stock issue on the remaining of a line on partial move
316
+
317
+ We have a move with 10 units.
318
+ 2 are reserved in a package. The remaining in another package.
319
+ We pick 1 of the first package and put it in a bin.
320
+ A new move line of 1 is created to pick in the first package: we
321
+ declare a stock out on it.
322
+ The first move line must be untouched, the second line for the remaining
323
+ should pick one more item in the other package.
324
+ """
325
+ package1 = self.env["stock.quant.package"].create({"name": "PACKAGE_1"})
326
+ package2 = self.env["stock.quant.package"].create({"name": "PACKAGE_2"})
327
+ self._update_qty_in_location(self.shelf1, self.product_a, 2, package=package1)
328
+ self._update_qty_in_location(self.shelf1, self.product_a, 200, package=package2)
329
+ self.move1._action_assign()
330
+ self.move2._action_assign()
331
+ self.move3._action_assign()
332
+ self._simulate_batch_selected(self.batch, fill_stock=False)
333
+ self.assertEqual(set(self.batch.picking_ids.mapped("state")), {"assigned"})
334
+
335
+ pick_line1, pick_line2 = self.move1.move_line_ids
336
+ new_line, __ = pick_line1._split_qty_to_be_done(1)
337
+ self._set_dest_package_and_done(pick_line1, self.dest_package)
338
+
339
+ self.assertEqual(pick_line1.reserved_qty, 1.0)
340
+ self.assertEqual(new_line.reserved_qty, 1.0)
341
+ self.assertEqual(pick_line2.reserved_qty, 8.0)
342
+ # on the third move, the operator can't pick anymore in shelf1
343
+ # because there is nothing inside, they declare a stock issue
344
+ self._stock_issue(new_line, next_line_func=lambda: pick_line2)
345
+
346
+ self.assertRecordValues(
347
+ # check that the first move line of the move was not altered
348
+ pick_line1,
349
+ [
350
+ {
351
+ "location_id": self.shelf1.id,
352
+ "qty_done": 1.0,
353
+ "result_package_id": self.dest_package.id,
354
+ }
355
+ ],
356
+ )
357
+ # the line on which we declared stock out does not exists
358
+ self.assertFalse(new_line.exists())
359
+ # the second line to pick has been raised to 9 instead of 8
360
+ # initially, to compensate the stock out
361
+ self.assertEqual(pick_line2.reserved_qty, 9.0)
362
+
363
+ # quant with stock out has been updated
364
+ self.assertEqual(package1.quant_ids.quantity, 1.0)