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,322 @@
1
+ # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+ import base64
4
+ import io
5
+
6
+ from markupsafe import Markup
7
+ from PIL import Image
8
+
9
+ from .test_actions_data_base import ActionsDataDetailCaseBase
10
+
11
+
12
+ def fake_colored_image(color="#4169E1", size=(800, 500)):
13
+ with io.BytesIO() as img_file:
14
+ Image.new("RGB", size, color).save(img_file, "JPEG")
15
+ img_file.seek(0)
16
+ return base64.b64encode(img_file.read())
17
+
18
+
19
+ class TestActionsDataDetailCase(ActionsDataDetailCaseBase):
20
+ def test_data_location(self):
21
+ location = self.stock_location
22
+ data = self.data_detail.location_detail(location)
23
+ self.assert_schema(self.schema_detail.location_detail(), data)
24
+ move_lines = self.env["stock.move.line"].search(
25
+ [
26
+ ("location_id", "child_of", location.id),
27
+ ("reserved_qty", ">", 0),
28
+ ("state", "not in", ("done", "cancel")),
29
+ ]
30
+ )
31
+ self.assertDictEqual(
32
+ data, self._expected_location_detail(location, move_lines=move_lines)
33
+ )
34
+
35
+ def test_data_packaging(self):
36
+ data = self.data_detail.packaging(self.packaging)
37
+ self.assert_schema(self.schema_detail.packaging(), data)
38
+ self.assertDictEqual(data, self._expected_packaging(self.packaging))
39
+
40
+ def test_data_lot(self):
41
+ lot = self.env["stock.lot"].create(
42
+ {
43
+ "product_id": self.product_b.id,
44
+ "company_id": self.env.company.id,
45
+ "ref": "#FOO",
46
+ "removal_date": "2020-05-20",
47
+ "expiration_date": "2020-05-31",
48
+ }
49
+ )
50
+ data = self.data_detail.lot_detail(lot)
51
+ self.assert_schema(self.schema_detail.lot_detail(), data)
52
+
53
+ expected = {
54
+ "id": lot.id,
55
+ "name": lot.name,
56
+ "ref": "#FOO",
57
+ "expiration_date": "2020-05-31T00:00:00",
58
+ "product": self._expected_product_detail(self.product_b, full=True),
59
+ }
60
+ # ignore time and TZ, we don't care here
61
+ self.assertEqual(data.pop("removal_date").split("T")[0], "2020-05-20")
62
+ self.assertEqual(data.pop("expire_date").split("T")[0], "2020-05-31")
63
+ self.assertDictEqual(data, expected)
64
+
65
+ def test_data_package(self):
66
+ package = self.move_a.move_line_ids.package_id
67
+ package.product_packaging_id = self.packaging.id
68
+ package.package_type_id = self.storage_type_pallet
69
+ # package.invalidate_recordset()
70
+ data = self.data_detail.package_detail(package, picking=self.picking)
71
+ self.assert_schema(self.schema_detail.package_detail(), data)
72
+
73
+ lines = self.env["stock.move.line"].search(
74
+ [("package_id", "=", package.id), ("state", "not in", ("done", "cancel"))]
75
+ )
76
+ pickings = lines.mapped("picking_id")
77
+ expected = {
78
+ "id": package.id,
79
+ "location": {
80
+ "id": package.location_id.id,
81
+ "name": package.location_id.display_name,
82
+ },
83
+ "name": package.name,
84
+ "move_line_count": 1,
85
+ "packaging": self.data_detail.packaging(package.product_packaging_id),
86
+ "weight": 20.0,
87
+ "pickings": self.data_detail.pickings(pickings),
88
+ "move_lines": self.data_detail.move_lines(lines),
89
+ "storage_type": {
90
+ "id": self.storage_type_pallet.id,
91
+ "name": self.storage_type_pallet.name,
92
+ },
93
+ }
94
+ self.assertDictEqual(data, expected)
95
+
96
+ def test_data_picking(self):
97
+ picking = self.picking
98
+ carrier = picking.carrier_id.search([], limit=1)
99
+ picking.write(
100
+ {
101
+ "origin": "created by test",
102
+ "note": Markup("<p>read me</p>"),
103
+ "priority": "1",
104
+ "carrier_id": carrier.id,
105
+ }
106
+ )
107
+ picking.move_ids.write({"date": "2020-05-13"})
108
+ data = self.data_detail.picking_detail(picking)
109
+ self.assert_schema(self.schema_detail.picking_detail(), data)
110
+ expected = {
111
+ "id": picking.id,
112
+ "move_line_count": 4,
113
+ "package_level_count": 2,
114
+ "bulk_line_count": 2,
115
+ "name": picking.name,
116
+ "note": Markup("<p>read me</p>"),
117
+ "origin": "created by test",
118
+ "ship_carrier": None,
119
+ "weight": 110.0,
120
+ "partner": {"id": self.customer.id, "name": self.customer.name},
121
+ "carrier": {"id": picking.carrier_id.id, "name": picking.carrier_id.name},
122
+ "priority": "Urgent",
123
+ "operation_type": {
124
+ "id": picking.picking_type_id.id,
125
+ "name": picking.picking_type_id.name,
126
+ },
127
+ "move_lines": self.data_detail.move_lines(picking.move_line_ids),
128
+ "picking_type_code": "outgoing",
129
+ }
130
+ self.assertEqual(data.pop("scheduled_date").split("T")[0], "2020-05-13")
131
+ self.assertDictEqual(data, expected)
132
+
133
+ def test_data_picking_with_progress(self):
134
+ picking = self.picking
135
+ carrier = picking.carrier_id.search([], limit=1)
136
+ picking.write(
137
+ {
138
+ "origin": "created by test",
139
+ "note": "read me",
140
+ "priority": "1",
141
+ "carrier_id": carrier.id,
142
+ }
143
+ )
144
+ picking.move_ids.write({"date": "2020-05-13"})
145
+ data = self.data_detail.picking_detail(picking, with_progress=True)
146
+ self.assert_schema(self.schema_detail.picking_detail(), data)
147
+ expected = {
148
+ "id": picking.id,
149
+ "move_line_count": 4,
150
+ "package_level_count": 2,
151
+ "bulk_line_count": 2,
152
+ "name": picking.name,
153
+ "note": Markup("<p>read me</p>"),
154
+ "origin": "created by test",
155
+ "ship_carrier": None,
156
+ "weight": 110.0,
157
+ "partner": {"id": self.customer.id, "name": self.customer.name},
158
+ "carrier": {"id": picking.carrier_id.id, "name": picking.carrier_id.name},
159
+ "priority": "Urgent",
160
+ "operation_type": {
161
+ "id": picking.picking_type_id.id,
162
+ "name": picking.picking_type_id.name,
163
+ },
164
+ "move_lines": self.data_detail.move_lines(picking.move_line_ids),
165
+ "picking_type_code": "outgoing",
166
+ "progress": 0.0,
167
+ }
168
+ self.assertEqual(data.pop("scheduled_date").split("T")[0], "2020-05-13")
169
+ self.assertDictEqual(data, expected)
170
+
171
+ def test_data_move_line_package(self):
172
+ move_line = self.move_a.move_line_ids
173
+ result_package = self.env["stock.quant.package"].create(
174
+ {"product_packaging_id": self.packaging.id}
175
+ )
176
+ move_line.write({"qty_done": 3.0, "result_package_id": result_package.id})
177
+ data = self.data_detail.move_line(move_line)
178
+ self.assert_schema(self.schema_detail.move_line(), data)
179
+ product = self.product_a.with_context(location=move_line.location_id.id)
180
+ expected = {
181
+ "id": move_line.id,
182
+ "qty_done": 3.0,
183
+ "quantity": move_line.reserved_uom_qty,
184
+ "product": self._expected_product_detail(product),
185
+ "lot": None,
186
+ "package_src": {
187
+ "id": move_line.package_id.id,
188
+ "name": move_line.package_id.name,
189
+ "move_line_count": 0,
190
+ "weight": 20.0,
191
+ "storage_type": None,
192
+ },
193
+ "package_dest": {
194
+ "id": result_package.id,
195
+ "name": result_package.name,
196
+ "move_line_count": 1,
197
+ "weight": 6.0,
198
+ "storage_type": None,
199
+ },
200
+ "location_src": self._expected_location(move_line.location_id),
201
+ "location_dest": self._expected_location(move_line.location_dest_id),
202
+ "priority": "1",
203
+ "progress": 30.0,
204
+ }
205
+ self.assertDictEqual(data, expected)
206
+
207
+ def test_data_move_line_lot(self):
208
+ move_line = self.move_b.move_line_ids
209
+ data = self.data_detail.move_line(move_line)
210
+ self.assert_schema(self.schema_detail.move_line(), data)
211
+ product = self.product_b.with_context(location=move_line.location_id.id)
212
+ expected = {
213
+ "id": move_line.id,
214
+ "qty_done": 0.0,
215
+ "quantity": move_line.reserved_uom_qty,
216
+ "product": self._expected_product_detail(product),
217
+ "lot": {
218
+ "id": move_line.lot_id.id,
219
+ "name": move_line.lot_id.name,
220
+ "ref": None,
221
+ "expiration_date": None,
222
+ },
223
+ "package_src": None,
224
+ "package_dest": None,
225
+ "location_src": self._expected_location(move_line.location_id),
226
+ "location_dest": self._expected_location(move_line.location_dest_id),
227
+ "priority": "1",
228
+ "progress": 0.0,
229
+ }
230
+ self.assertDictEqual(data, expected)
231
+
232
+ def test_data_move_line_package_lot(self):
233
+ move_line = self.move_c.move_line_ids
234
+ data = self.data_detail.move_line(move_line)
235
+ self.assert_schema(self.schema_detail.move_line(), data)
236
+ product = self.product_c.with_context(location=move_line.location_id.id)
237
+ expected = {
238
+ "id": move_line.id,
239
+ "qty_done": 0.0,
240
+ "quantity": move_line.reserved_uom_qty,
241
+ "product": self._expected_product_detail(product),
242
+ "lot": {
243
+ "id": move_line.lot_id.id,
244
+ "name": move_line.lot_id.name,
245
+ "ref": None,
246
+ "expiration_date": None,
247
+ },
248
+ "package_src": {
249
+ "id": move_line.package_id.id,
250
+ "name": move_line.package_id.name,
251
+ "move_line_count": 1,
252
+ "weight": 30.0,
253
+ "storage_type": None,
254
+ },
255
+ "package_dest": {
256
+ "id": move_line.result_package_id.id,
257
+ "name": move_line.result_package_id.name,
258
+ "move_line_count": 1,
259
+ "weight": 0.0,
260
+ "storage_type": None,
261
+ },
262
+ "location_src": self._expected_location(move_line.location_id),
263
+ "location_dest": self._expected_location(move_line.location_dest_id),
264
+ "priority": "1",
265
+ "progress": 0.0,
266
+ }
267
+ self.assertDictEqual(data, expected)
268
+
269
+ def test_data_move_line_raw(self):
270
+ move_line = self.move_d.move_line_ids
271
+ data = self.data_detail.move_line(move_line)
272
+ self.assert_schema(self.schema_detail.move_line(), data)
273
+ product = self.product_d.with_context(location=move_line.location_id.id)
274
+ expected = {
275
+ "id": move_line.id,
276
+ "qty_done": 0.0,
277
+ "quantity": move_line.reserved_uom_qty,
278
+ "product": self._expected_product_detail(product),
279
+ "lot": None,
280
+ "package_src": None,
281
+ "package_dest": None,
282
+ "location_src": self._expected_location(move_line.location_id),
283
+ "location_dest": self._expected_location(move_line.location_dest_id),
284
+ "priority": "1",
285
+ "progress": 0.0,
286
+ }
287
+ self.assertDictEqual(data, expected)
288
+
289
+ def test_product(self):
290
+ move_line = self.move_b.move_line_ids
291
+ product = move_line.product_id.with_context(location=move_line.location_id.id)
292
+ Partner = self.env["res.partner"].sudo()
293
+ manuf = Partner.create({"name": "Manuf 1"})
294
+ product.sudo().write(
295
+ {
296
+ "image_128": fake_colored_image(size=(128, 128)),
297
+ "manufacturer_id": manuf.id,
298
+ }
299
+ )
300
+ vendor_a = Partner.create({"name": "Supplier A"})
301
+ vendor_b = Partner.create({"name": "Supplier B"})
302
+ SupplierInfo = self.env["product.supplierinfo"].sudo()
303
+ SupplierInfo.create(
304
+ {
305
+ "partner_id": vendor_a.id,
306
+ "product_tmpl_id": product.product_tmpl_id.id,
307
+ "product_id": product.id,
308
+ "product_code": "SUPP1",
309
+ }
310
+ )
311
+ SupplierInfo.create(
312
+ {
313
+ "partner_id": vendor_b.id,
314
+ "product_tmpl_id": product.product_tmpl_id.id,
315
+ "product_id": product.id,
316
+ "product_code": "SUPP2",
317
+ }
318
+ )
319
+ data = self.data_detail.product_detail(product)
320
+ self.assert_schema(self.schema_detail.product_detail(), data)
321
+ expected = self._expected_product_detail(product, full=True)
322
+ self.assertDictEqual(data, expected)
@@ -0,0 +1,248 @@
1
+ # Copyright 2021 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+ # @author Simone Orsi <simahawk@gmail.com>
4
+
5
+ from .common import CommonCase
6
+
7
+
8
+ # pylint: disable=missing-return
9
+ class TestSearchBaseCase(CommonCase):
10
+ @classmethod
11
+ def setUpClassVars(cls):
12
+ super().setUpClassVars()
13
+ with cls.work_on_actions(cls) as work:
14
+ cls.search = work.component(usage="search")
15
+
16
+
17
+ class TestSearchCase(TestSearchBaseCase):
18
+ def test_search_location(self):
19
+ rec = self.customer_location
20
+ handler = self.search.location_from_scan
21
+ self.assertEqual(handler(rec.barcode), rec)
22
+ self.assertEqual(handler(rec.name), rec)
23
+ self.assertEqual(handler(False), rec.browse())
24
+ self.assertEqual(handler("NONE"), rec.browse())
25
+
26
+ def test_search_location_with_limit(self):
27
+ rec = self.customer_location
28
+ rec2 = self.customer_location.sudo().copy(
29
+ {"barcode": "CUSTOMERS2", "name": "Customers"}
30
+ )
31
+ handler = self.search.location_from_scan
32
+ res = handler("Customers", 2)
33
+ self.assertEqual(res, rec + rec2)
34
+
35
+ def test_search_package(self):
36
+ rec = self.env["stock.quant.package"].sudo().create({"name": "1234"})
37
+ handler = self.search.package_from_scan
38
+ self.assertEqual(handler(rec.name), rec)
39
+ self.assertEqual(handler(False), rec.browse())
40
+ self.assertEqual(handler("NONE"), rec.browse())
41
+
42
+ def test_search_picking(self):
43
+ ptype = self.env.ref("shopfloor.picking_type_single_pallet_transfer_demo")
44
+ rec = self._create_picking(picking_type=ptype)
45
+ handler = self.search.picking_from_scan
46
+ self.assertEqual(handler(rec.name), rec)
47
+ self.assertEqual(handler(False), rec.browse())
48
+ self.assertEqual(handler("NONE"), rec.browse())
49
+
50
+ def test_search_product(self):
51
+ rec = self.product_a
52
+ handler = self.search.product_from_scan
53
+ self.assertEqual(handler(rec.barcode), rec)
54
+ self.assertEqual(handler(False), rec.browse())
55
+ self.assertEqual(handler("NONE"), rec.browse())
56
+ # It is not possible to search a product by packaging
57
+ packaging = self.product_a_packaging
58
+ self.assertFalse(handler(packaging.barcode))
59
+
60
+ def test_search_lot_number_unique(self):
61
+ rec = (
62
+ self.env["stock.lot"]
63
+ .sudo()
64
+ .create(
65
+ {"product_id": self.product_a.id, "company_id": self.env.company.id}
66
+ )
67
+ )
68
+ handler = self.search.lot_from_scan
69
+ self.assertEqual(handler(rec.name, products=self.product_a), rec)
70
+ self.assertEqual(handler(False), rec.browse())
71
+ self.assertEqual(handler("NONE"), rec.browse())
72
+
73
+ def test_search_lot_number_shared_with_multiple_products(self):
74
+ lot_model = self.env["stock.lot"].sudo()
75
+ lots = (
76
+ lot_model.create(
77
+ {
78
+ "name": "TEST",
79
+ "product_id": self.product_a.id,
80
+ "company_id": self.env.company.id,
81
+ }
82
+ ),
83
+ lot_model.create(
84
+ {
85
+ "name": "TEST",
86
+ "product_id": self.product_b.id,
87
+ "company_id": self.env.company.id,
88
+ }
89
+ ),
90
+ )
91
+ handler = self.search.lot_from_scan
92
+ self.assertEqual(handler(lots[0].name, products=self.product_a), lots[0])
93
+ self.assertEqual(handler(lots[1].name, products=self.product_a), lots[0])
94
+ self.assertEqual(handler(lots[0].name, products=self.product_b), lots[1])
95
+ self.assertEqual(handler(lots[1].name, products=self.product_b), lots[1])
96
+
97
+ def test_search_generic_packaging(self):
98
+ rec = (
99
+ self.env["product.packaging"]
100
+ .sudo()
101
+ .create({"name": "TEST PKG", "barcode": "1234"})
102
+ )
103
+ handler = self.search.generic_packaging_from_scan
104
+ self.assertEqual(handler(rec.barcode), rec)
105
+ self.assertEqual(handler(False), rec.browse())
106
+ self.assertEqual(handler("NONE"), rec.browse())
107
+
108
+
109
+ class TestFindCase(TestSearchBaseCase):
110
+ def test_find_api(self):
111
+ self.assertEqual(self.search.find(False).record, None)
112
+ self.assertEqual(self.search.find("NONE").record, None)
113
+ self.assertEqual(self.search.find("foo", types=("not_existing",)).record, None)
114
+ # TODO: test SearchResult class
115
+
116
+ def test_find_location(self):
117
+ rec = self.customer_location
118
+ res = self.search.find(rec.barcode, types=("location",))
119
+ self.assertEqual(res.record, rec)
120
+ res = self.search.find(rec.name, types=("location",))
121
+ self.assertEqual(res.record, rec)
122
+
123
+ def test_find_location_with_limit(self):
124
+ rec = self.customer_location
125
+ rec2 = self.customer_location.sudo().copy(
126
+ {"barcode": "CUSTOMERS2", "name": "Customers"}
127
+ )
128
+ res = self.search.find("Customers", types=("location",))
129
+ self.assertEqual(res.records, None)
130
+ res = self.search.find(
131
+ "Customers", types=("location",), handler_kw=dict(location=dict(limit=2))
132
+ )
133
+ self.assertEqual(res.records, rec + rec2)
134
+
135
+ def test_find_package(self):
136
+ rec = self.env["stock.quant.package"].sudo().create({"name": "1234"})
137
+ res = self.search.find(rec.name, types=("package",))
138
+ self.assertEqual(res.record, rec)
139
+
140
+ def test_find_picking(self):
141
+ ptype = self.env.ref("shopfloor.picking_type_single_pallet_transfer_demo")
142
+ rec = self._create_picking(picking_type=ptype)
143
+ res = self.search.find(rec.name, types=("picking",))
144
+ self.assertEqual(res.record, rec)
145
+
146
+ def test_find_product(self):
147
+ rec = self.product_a
148
+ res = self.search.find(rec.barcode, types=("product",))
149
+ self.assertEqual(res.record, rec)
150
+ # It is not possible to search a product by packaging
151
+ packaging = self.product_a_packaging
152
+ res = self.search.find(packaging.barcode, types=("product",))
153
+ self.assertEqual(res.record, None)
154
+
155
+ def test_find_product_packaging(self):
156
+ rec = self.product_a_packaging
157
+ res = self.search.find(rec.barcode, types=("packaging",))
158
+ self.assertEqual(res.record, rec)
159
+
160
+ def test_find_lot_number_unique(self):
161
+ rec = (
162
+ self.env["stock.lot"]
163
+ .sudo()
164
+ .create(
165
+ {"product_id": self.product_a.id, "company_id": self.env.company.id}
166
+ )
167
+ )
168
+ res = self.search.find(
169
+ rec.name, types=("lot",), handler_kw=dict(lot=dict(products=self.product_a))
170
+ )
171
+ self.assertEqual(res.record, rec)
172
+
173
+ def test_find_lot_number_shared_with_multiple_products(self):
174
+ lot_model = self.env["stock.lot"].sudo()
175
+ lots = (
176
+ lot_model.create(
177
+ {
178
+ "name": "TEST",
179
+ "product_id": self.product_a.id,
180
+ "company_id": self.env.company.id,
181
+ }
182
+ ),
183
+ lot_model.create(
184
+ {
185
+ "name": "TEST",
186
+ "product_id": self.product_b.id,
187
+ "company_id": self.env.company.id,
188
+ }
189
+ ),
190
+ )
191
+ res = self.search.find(
192
+ lots[0].name,
193
+ types=("lot",),
194
+ handler_kw=dict(lot=dict(products=self.product_a)),
195
+ )
196
+ self.assertEqual(res.record, lots[0])
197
+ res = self.search.find(
198
+ lots[1].name,
199
+ types=("lot",),
200
+ handler_kw=dict(lot=dict(products=self.product_a)),
201
+ )
202
+ self.assertEqual(res.record, lots[0])
203
+ res = self.search.find(
204
+ lots[0].name,
205
+ types=("lot",),
206
+ handler_kw=dict(lot=dict(products=self.product_b)),
207
+ )
208
+ self.assertEqual(res.record, lots[1])
209
+ res = self.search.find(
210
+ lots[1].name,
211
+ types=("lot",),
212
+ handler_kw=dict(lot=dict(products=self.product_b)),
213
+ )
214
+ self.assertEqual(res.record, lots[1])
215
+
216
+ def test_find_chain(self):
217
+ prod = self.product_a
218
+ # prod last type -> found
219
+ res = self.search.find(
220
+ prod.barcode,
221
+ types=(
222
+ "location",
223
+ "lot",
224
+ "product",
225
+ ),
226
+ )
227
+ self.assertEqual(res.record, prod)
228
+ loc = self.customer_location.sudo().copy({"barcode": prod.barcode})
229
+ # prod last type but a location w/ the same name exists -> location found
230
+ res = self.search.find(
231
+ prod.barcode,
232
+ types=(
233
+ "location",
234
+ "lot",
235
+ "product",
236
+ ),
237
+ )
238
+ self.assertEqual(res.record, loc)
239
+ # change types order -> prod found
240
+ res = self.search.find(
241
+ prod.barcode,
242
+ types=(
243
+ "product",
244
+ "location",
245
+ "lot",
246
+ ),
247
+ )
248
+ self.assertEqual(res.record, prod)
@@ -0,0 +1,48 @@
1
+ # Copyright 2023 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+ # from odoo.tests.common import Form
4
+
5
+ from .common import CommonCase
6
+
7
+
8
+ # pylint: disable=missing-return
9
+ class TestActionsStock(CommonCase):
10
+ """Tests covering methods to work on stock operations."""
11
+
12
+ @classmethod
13
+ def setUpClass(cls):
14
+ super().setUpClass()
15
+ with cls.work_on_actions(cls) as work:
16
+ cls.stock = work.component(usage="stock")
17
+ cls.picking = cls._create_picking(
18
+ lines=[(cls.product_a, 10), (cls.product_b, 10)], confirm=True
19
+ )
20
+ cls.move0 = cls.picking.move_ids[0]
21
+ cls.move1 = cls.picking.move_ids[1]
22
+ cls._fill_stock_for_moves(cls.move0)
23
+ cls._fill_stock_for_moves(cls.move1)
24
+ cls.picking.action_assign()
25
+
26
+ @classmethod
27
+ def setUpClassVars(cls):
28
+ super().setUpClassVars()
29
+ cls.wh = cls.env.ref("stock.warehouse0")
30
+ cls.picking_type = cls.wh.out_type_id
31
+
32
+ def test_unmark_move_line_as_picked(self):
33
+ """Check unmarking line as picked works."""
34
+ lines_picked = self.picking.move_line_ids
35
+ # all lines (two) are picked
36
+ self.stock.mark_move_line_as_picked(lines_picked)
37
+ self.assertTrue(self.picking.user_id)
38
+ # unpick one line
39
+ line_unpicked = lines_picked[0]
40
+ self.stock.unmark_move_line_as_picked(line_unpicked)
41
+ # because not all lines of the picking have to be unpicked
42
+ # they should be split to a new picking
43
+ picking_not_assigned = line_unpicked.picking_id
44
+ self.assertTrue(line_unpicked.picking_id != lines_picked.picking_id)
45
+ self.assertTrue(self.picking.user_id)
46
+ self.assertTrue(self.picking.move_line_ids.shopfloor_user_id)
47
+ self.assertFalse(picking_not_assigned.move_line_ids.shopfloor_user_id)
48
+ self.assertFalse(picking_not_assigned.user_id)
@@ -0,0 +1,67 @@
1
+ # Copyright 2023 Camptocamp SA (http://www.camptocamp.com)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+ from .test_checkout_base import CheckoutCommonCase
4
+
5
+
6
+ class CheckoutAutoPostCase(CheckoutCommonCase):
7
+ def test_auto_posting(self):
8
+ self.menu.sudo().auto_post_line = True
9
+ picking = self._create_picking(
10
+ lines=[(self.product_a, 10), (self.product_b, 20), (self.product_c, 30)]
11
+ )
12
+ self._fill_stock_for_moves(picking.move_ids)
13
+ picking.action_assign()
14
+ selected_move_line_a = picking.move_line_ids.filtered(
15
+ lambda l: l.product_id == self.product_a
16
+ )
17
+ selected_move_line_a.qty_done = 7
18
+ selected_move_line_b = picking.move_line_ids.filtered(
19
+ lambda l: l.product_id == self.product_b
20
+ )
21
+ selected_move_line_b.qty_done = 9
22
+ selected_move_line_c = picking.move_line_ids.filtered(
23
+ lambda l: l.product_id == self.product_c
24
+ )
25
+
26
+ # User has selected 7 units out of 10 for product_a,
27
+ # and 9 units out of 20 for product_b.
28
+ # We would expect a split picking to be created with those two lines and qtys done.
29
+ self.service.dispatch(
30
+ "scan_package_action",
31
+ params={
32
+ "picking_id": picking.id,
33
+ "selected_line_ids": [selected_move_line_a.id, selected_move_line_b.id],
34
+ "barcode": self.delivery_packaging.barcode,
35
+ },
36
+ )
37
+
38
+ # Check that two new lines for products a and b are in a split picking,
39
+ # and the line for product c remained in the original picking.
40
+ self.assertNotEqual(picking, selected_move_line_a.picking_id)
41
+ self.assertEqual(
42
+ selected_move_line_a.picking_id, selected_move_line_b.picking_id
43
+ )
44
+ self.assertEqual(picking, selected_move_line_c.picking_id)
45
+
46
+ # The lines in the new picking must have the expected qty_done,
47
+ # and the split picking must be marked as "done".
48
+ self.assertEqual(selected_move_line_a.qty_done, 7)
49
+ self.assertEqual(selected_move_line_b.qty_done, 9)
50
+ self.assertEqual(selected_move_line_a.picking_id.state, "done")
51
+
52
+ # In the original picking, we should have three lines:
53
+ # - the original line for product c, unchanged;
54
+ # - two lines (products a and b) with the non-split qtys.
55
+ line_a_in_original_picking = picking.move_line_ids.filtered(
56
+ lambda l: l.product_id == selected_move_line_a.product_id
57
+ )
58
+ line_b_in_original_picking = picking.move_line_ids.filtered(
59
+ lambda l: l.product_id == selected_move_line_b.product_id
60
+ )
61
+ self.assertEqual(line_a_in_original_picking.reserved_uom_qty, 3)
62
+ self.assertEqual(line_b_in_original_picking.reserved_uom_qty, 11)
63
+ self.assertEqual(selected_move_line_c.reserved_uom_qty, 30)
64
+
65
+ self.assertEqual(line_a_in_original_picking.qty_done, 0)
66
+ self.assertEqual(line_b_in_original_picking.qty_done, 0)
67
+ self.assertEqual(selected_move_line_c.qty_done, 0)