odoo-addon-shopfloor 16.0.2.2.0__py3-none-any.whl → 16.0.2.2.1.5__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 (28) hide show
  1. odoo/addons/shopfloor/README.rst +1 -1
  2. odoo/addons/shopfloor/__manifest__.py +1 -1
  3. odoo/addons/shopfloor/actions/move_line_search.py +125 -33
  4. odoo/addons/shopfloor/data/shopfloor_scenario_data.xml +7 -2
  5. odoo/addons/shopfloor/i18n/ca.po +106 -4
  6. odoo/addons/shopfloor/i18n/de.po +104 -2
  7. odoo/addons/shopfloor/i18n/es_AR.po +108 -6
  8. odoo/addons/shopfloor/i18n/it.po +108 -6
  9. odoo/addons/shopfloor/i18n/pt_BR.po +104 -2
  10. odoo/addons/shopfloor/i18n/shopfloor.pot +102 -0
  11. odoo/addons/shopfloor/migrations/16.0.2.2.1/post-init_search_move_line_options.py +33 -0
  12. odoo/addons/shopfloor/models/shopfloor_menu.py +85 -0
  13. odoo/addons/shopfloor/services/location_content_transfer.py +20 -41
  14. odoo/addons/shopfloor/services/menu.py +6 -3
  15. odoo/addons/shopfloor/services/service.py +28 -2
  16. odoo/addons/shopfloor/services/zone_picking.py +2 -2
  17. odoo/addons/shopfloor/static/description/index.html +1 -1
  18. odoo/addons/shopfloor/tests/__init__.py +2 -0
  19. odoo/addons/shopfloor/tests/test_actions_search_move_line.py +128 -0
  20. odoo/addons/shopfloor/tests/test_location_content_transfer_get_work.py +41 -0
  21. odoo/addons/shopfloor/tests/test_location_content_transfer_mix.py +2 -2
  22. odoo/addons/shopfloor/tests/test_menu_contrains.py +40 -0
  23. odoo/addons/shopfloor/tests/test_menu_counters.py +112 -2
  24. odoo/addons/shopfloor/views/shopfloor_menu.xml +54 -0
  25. {odoo_addon_shopfloor-16.0.2.2.0.dist-info → odoo_addon_shopfloor-16.0.2.2.1.5.dist-info}/METADATA +2 -2
  26. {odoo_addon_shopfloor-16.0.2.2.0.dist-info → odoo_addon_shopfloor-16.0.2.2.1.5.dist-info}/RECORD +28 -25
  27. {odoo_addon_shopfloor-16.0.2.2.0.dist-info → odoo_addon_shopfloor-16.0.2.2.1.5.dist-info}/WHEEL +0 -0
  28. {odoo_addon_shopfloor-16.0.2.2.0.dist-info → odoo_addon_shopfloor-16.0.2.2.1.5.dist-info}/top_level.txt +0 -0
@@ -110,6 +110,32 @@ msgid ""
110
110
  "destination'."
111
111
  msgstr ""
112
112
 
113
+ #. module: shopfloor
114
+ #: model_terms:ir.ui.view,arch_db:shopfloor.shopfloor_menu_form_view
115
+ msgid ""
116
+ "<code>get_sort_key_assigned_to_current_user</code>: Return sort key to get "
117
+ "line assigned to current user first. (return a tuple)"
118
+ msgstr ""
119
+
120
+ #. module: shopfloor
121
+ #: model_terms:ir.ui.view,arch_db:shopfloor.shopfloor_menu_form_view
122
+ msgid ""
123
+ "<code>get_sort_key_location</code>: Return sort key to sort by location. "
124
+ "(return a tuple)"
125
+ msgstr ""
126
+
127
+ #. module: shopfloor
128
+ #: model_terms:ir.ui.view,arch_db:shopfloor.shopfloor_menu_form_view
129
+ msgid ""
130
+ "<code>get_sort_key_priority</code>: Return sort key to sort by priority. "
131
+ "(return a tuple)"
132
+ msgstr ""
133
+
134
+ #. module: shopfloor
135
+ #: model_terms:ir.ui.view,arch_db:shopfloor.shopfloor_menu_form_view
136
+ msgid "<code>line</code>: The move line for with the key must be computed."
137
+ msgstr ""
138
+
113
139
  #. module: shopfloor
114
140
  #: model:ir.model,name:shopfloor.model_shopfloor_app
115
141
  msgid "A Shopfloor application"
@@ -127,6 +153,11 @@ msgstr ""
127
153
  msgid "Activate Zero Check"
128
154
  msgstr ""
129
155
 
156
+ #. module: shopfloor
157
+ #: model:ir.model.fields,field_description:shopfloor.field_shopfloor_menu__move_line_search_additional_domain
158
+ msgid "Additional domain used when searching move lines"
159
+ msgstr ""
160
+
130
161
  #. module: shopfloor
131
162
  #: model:ir.model,name:shopfloor.model_shopfloor_priority_postpone_mixin
132
163
  msgid "Adds shopfloor priority/postpone fields"
@@ -324,6 +355,35 @@ msgstr ""
324
355
  msgid "Creation of moves is not allowed for menu {}."
325
356
  msgstr ""
326
357
 
358
+ #. module: shopfloor
359
+ #: model:ir.model.fields.selection,name:shopfloor.selection__shopfloor_menu__move_line_search_sort_order__custom_code
360
+ msgid "Custom code"
361
+ msgstr ""
362
+
363
+ #. module: shopfloor
364
+ #: model_terms:ir.ui.view,arch_db:shopfloor.shopfloor_menu_form_view
365
+ msgid "Custom code to construct the key to sort move lines"
366
+ msgstr ""
367
+
368
+ #. module: shopfloor
369
+ #: model:ir.model.fields,field_description:shopfloor.field_shopfloor_menu__move_line_search_sort_order_custom_code
370
+ msgid "Custom sort key code"
371
+ msgstr ""
372
+
373
+ #. module: shopfloor
374
+ #. odoo-python
375
+ #: code:addons/shopfloor/models/shopfloor_menu.py:0
376
+ #, python-format
377
+ msgid "Custom sort key code is only allowed when 'Custom code' is selected."
378
+ msgstr ""
379
+
380
+ #. module: shopfloor
381
+ #. odoo-python
382
+ #: code:addons/shopfloor/models/shopfloor_menu.py:0
383
+ #, python-format
384
+ msgid "Custom sort key code is required when 'Custom code' is selected."
385
+ msgstr ""
386
+
327
387
  #. module: shopfloor
328
388
  #: model:ir.model.fields,field_description:shopfloor.field_stock_move_line__date_planned
329
389
  msgid "Date Scheduled"
@@ -460,6 +520,11 @@ msgstr ""
460
520
  msgid "Lines have different destination location."
461
521
  msgstr ""
462
522
 
523
+ #. module: shopfloor
524
+ #: model:ir.model.fields.selection,name:shopfloor.selection__shopfloor_menu__move_line_search_sort_order__location
525
+ msgid "Location"
526
+ msgstr ""
527
+
463
528
  #. module: shopfloor
464
529
  #. odoo-python
465
530
  #: code:addons/shopfloor/actions/message.py:0
@@ -585,6 +650,16 @@ msgstr ""
585
650
  msgid "Move Line Count"
586
651
  msgstr ""
587
652
 
653
+ #. module: shopfloor
654
+ #: model:ir.model.fields,field_description:shopfloor.field_shopfloor_menu__move_line_search_additional_domain_is_possible
655
+ msgid "Move Line Search Additional Domain Is Possible"
656
+ msgstr ""
657
+
658
+ #. module: shopfloor
659
+ #: model:ir.model.fields,field_description:shopfloor.field_shopfloor_menu__move_line_search_sort_order_is_possible
660
+ msgid "Move Line Search Sort Order Is Possible"
661
+ msgstr ""
662
+
588
663
  #. module: shopfloor
589
664
  #. odoo-python
590
665
  #: code:addons/shopfloor/actions/message.py:0
@@ -1086,6 +1161,11 @@ msgstr ""
1086
1161
  msgid "Prepackaged Product Is Possible"
1087
1162
  msgstr ""
1088
1163
 
1164
+ #. module: shopfloor
1165
+ #: model:ir.model.fields.selection,name:shopfloor.selection__shopfloor_menu__move_line_search_sort_order__priority
1166
+ msgid "Priority"
1167
+ msgstr ""
1168
+
1089
1169
  #. module: shopfloor
1090
1170
  #: model:ir.model.fields,field_description:shopfloor.field_shopfloor_menu__allow_prepackaged_product
1091
1171
  msgid "Process as pre-packaged"
@@ -1147,6 +1227,11 @@ msgstr ""
1147
1227
  msgid "Product(s) processed as raw product(s)"
1148
1228
  msgstr ""
1149
1229
 
1230
+ #. module: shopfloor
1231
+ #: model:ir.model.fields,help:shopfloor.field_shopfloor_menu__move_line_search_sort_order_custom_code
1232
+ msgid "Python code to sort move lines. "
1233
+ msgstr ""
1234
+
1150
1235
  #. module: shopfloor
1151
1236
  #: model:ir.model,name:shopfloor.model_stock_quant
1152
1237
  msgid "Quants"
@@ -1421,6 +1506,11 @@ msgstr ""
1421
1506
  msgid "Someone is already working on these transfers"
1422
1507
  msgstr ""
1423
1508
 
1509
+ #. module: shopfloor
1510
+ #: model:ir.model.fields,field_description:shopfloor.field_shopfloor_menu__move_line_search_sort_order
1511
+ msgid "Sort method used when searching move lines"
1512
+ msgstr ""
1513
+
1424
1514
  #. module: shopfloor
1425
1515
  #: model:ir.model.fields,field_description:shopfloor.field_stock_location__source_move_line_ids
1426
1516
  msgid "Source Move Line"
@@ -1735,6 +1825,18 @@ msgstr ""
1735
1825
  msgid "This transfer does not exist or is not available anymore."
1736
1826
  msgstr ""
1737
1827
 
1828
+ #. module: shopfloor
1829
+ #: model_terms:ir.ui.view,arch_db:shopfloor.shopfloor_menu_form_view
1830
+ msgid "Three variables are available:"
1831
+ msgstr ""
1832
+
1833
+ #. module: shopfloor
1834
+ #: model_terms:ir.ui.view,arch_db:shopfloor.shopfloor_menu_form_view
1835
+ msgid ""
1836
+ "To assign the key value: <code>key = "
1837
+ "get_sort_key_assigned_to_current_user(line) + (line.x, )</code>"
1838
+ msgstr ""
1839
+
1738
1840
  #. module: shopfloor
1739
1841
  #: model:ir.model.fields,field_description:shopfloor.field_stock_picking__total_weight
1740
1842
  #: model:ir.model.fields,field_description:shopfloor.field_stock_picking_batch__total_weight
@@ -0,0 +1,33 @@
1
+ # Copyright 2024 ACSONE SA/NV (http://acsone.eu)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+
4
+ import json
5
+ import logging
6
+
7
+ from odoo import SUPERUSER_ID, api
8
+
9
+ _logger = logging.getLogger(__name__)
10
+
11
+
12
+ def migrate(cr, version):
13
+ if not version:
14
+ return
15
+ env = api.Environment(cr, SUPERUSER_ID, {})
16
+ zone_picking = env.ref("shopfloor.scenario_zone_picking")
17
+ _update_scenario_options(zone_picking, sort_order=False, additional_domain=True)
18
+ location_content_transfer = env.ref("shopfloor.scenario_location_content_transfer")
19
+ _update_scenario_options(
20
+ location_content_transfer, sort_order=True, additional_domain=True
21
+ )
22
+
23
+
24
+ def _update_scenario_options(scenario, sort_order=True, additional_domain=True):
25
+ options = scenario.options
26
+ options["allow_move_line_search_sort_order"] = sort_order
27
+ options["allow_move_line_search_additional_domain"] = additional_domain
28
+ options_edit = json.dumps(options or {}, indent=4, sort_keys=True)
29
+ scenario.write({"options_edit": options_edit})
30
+ _logger.info(
31
+ "Option allow_alternative_destination_package added to scenario %s",
32
+ scenario.name,
33
+ )
@@ -1,6 +1,8 @@
1
1
  # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # Copyright 2024 ACSONE SA/NV (http://www.acsone.eu)
2
3
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
4
  from odoo import _, api, exceptions, fields, models
5
+ from odoo.tools.safe_eval import test_python_expr
4
6
 
5
7
  PICK_PACK_SAME_TIME_HELP = """
6
8
  If you tick this box, while picking goods from a location
@@ -227,6 +229,28 @@ class ShopfloorMenu(models.Model):
227
229
  compute="_compute_allow_alternative_destination_package_is_possible"
228
230
  )
229
231
 
232
+ move_line_search_additional_domain_is_possible = fields.Boolean(
233
+ compute="_compute_move_line_search_additional_domain_is_possible"
234
+ )
235
+ move_line_search_additional_domain = fields.Char(
236
+ string="Additional domain used when searching move lines"
237
+ )
238
+ move_line_search_sort_order_is_possible = fields.Boolean(
239
+ compute="_compute_move_line_search_sort_order_is_possible"
240
+ )
241
+ move_line_search_sort_order = fields.Selection(
242
+ selection=[
243
+ ("priority", "Priority"),
244
+ ("location", "Location"),
245
+ ("custom_code", "Custom code"),
246
+ ],
247
+ string="Sort method used when searching move lines",
248
+ default="priority",
249
+ )
250
+ move_line_search_sort_order_custom_code = fields.Text(
251
+ string="Custom sort key code", help="Python code to sort move lines. "
252
+ )
253
+
230
254
  @api.onchange("unload_package_at_destination")
231
255
  def _onchange_unload_package_at_destination(self):
232
256
  # Uncheck pick_pack_same_time when unload_package_at_destination is set to True
@@ -458,3 +482,64 @@ class ShopfloorMenu(models.Model):
458
482
  menu.allow_alternative_destination_package_is_possible = (
459
483
  menu.scenario_id.has_option("allow_alternative_destination_package")
460
484
  )
485
+
486
+ @api.depends("scenario_id")
487
+ def _compute_move_line_search_additional_domain_is_possible(self):
488
+ for menu in self:
489
+ menu.move_line_search_additional_domain_is_possible = (
490
+ menu.scenario_id.has_option("allow_move_line_search_additional_domain")
491
+ )
492
+
493
+ @api.depends("scenario_id")
494
+ def _compute_move_line_search_sort_order_is_possible(self):
495
+ for menu in self:
496
+ menu.move_line_search_sort_order_is_possible = menu.scenario_id.has_option(
497
+ "allow_move_line_search_sort_order"
498
+ )
499
+
500
+ @api.constrains(
501
+ "move_line_search_sort_order", "move_line_search_sort_order_custom_code"
502
+ )
503
+ def _check_move_line_search_sort_order_custom_code(self):
504
+ for menu in self:
505
+ if (
506
+ menu.move_line_search_sort_order == "custom_code"
507
+ and not menu.move_line_search_sort_order_custom_code
508
+ ):
509
+ raise exceptions.ValidationError(
510
+ _(
511
+ "Custom sort key code is required when 'Custom code' is selected."
512
+ )
513
+ )
514
+ if (
515
+ menu.move_line_search_sort_order != "custom_code"
516
+ and menu.move_line_search_sort_order_custom_code
517
+ ):
518
+ raise exceptions.ValidationError(
519
+ _(
520
+ "Custom sort key code is only allowed when 'Custom code' is selected."
521
+ )
522
+ )
523
+ code = (
524
+ menu.move_line_search_sort_order_custom_code
525
+ and menu.move_line_search_sort_order_custom_code.strip()
526
+ )
527
+ if code:
528
+ msg = test_python_expr(code, mode="exec")
529
+ if msg:
530
+ raise exceptions.ValidationError(msg)
531
+
532
+ @api.model_create_multi
533
+ def create(self, vals_list):
534
+ for vals in vals_list:
535
+ if vals.get("move_line_search_sort_order", "") != "custom_code":
536
+ vals["move_line_search_sort_order_custom_code"] = ""
537
+ return super().create(vals_list)
538
+
539
+ def write(self, vals):
540
+ if (
541
+ "move_line_search_sort_order" in vals
542
+ and vals["move_line_search_sort_order"] != "custom_code"
543
+ ):
544
+ vals["move_line_search_sort_order_custom_code"] = ""
545
+ return super().write(vals)
@@ -2,7 +2,8 @@
2
2
  # Copyright 2020-2022 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
3
3
  # Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
4
4
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
5
- from odoo import _, fields
5
+ from odoo import _
6
+ from odoo.fields import first
6
7
 
7
8
  from odoo.addons.base_rest.components.service import to_int
8
9
  from odoo.addons.component.core import Component
@@ -208,24 +209,6 @@ class LocationContentTransfer(Component):
208
209
  response = self._recover_started_picking()
209
210
  return response or self._response_for_start()
210
211
 
211
- def _find_location_move_lines_domain(self, location):
212
- return [
213
- ("location_id", "=", location.id),
214
- ("qty_done", "=", 0),
215
- ("state", "in", ("assigned", "partially_available")),
216
- ("picking_id.user_id", "in", (False, self.env.uid)),
217
- ("picking_id.state", "=", "assigned"),
218
- ]
219
-
220
- def _find_location_move_lines_from_scan_location(self, *args, **kwargs):
221
- return self._find_location_move_lines(*args, **kwargs)
222
-
223
- def _find_location_move_lines(self, location):
224
- """Find lines that potentially are to move in the location"""
225
- return self.env["stock.move.line"].search(
226
- self._find_location_move_lines_domain(location)
227
- )
228
-
229
212
  def _create_moves_from_location(self, location):
230
213
  # get all quants from the scanned location
231
214
  quants = self.env["stock.quant"].search(
@@ -251,24 +234,12 @@ class LocationContentTransfer(Component):
251
234
  return self.env["stock.move"].create(move_vals_list)
252
235
 
253
236
  def _find_location_to_work_from(self):
254
- location = self.env["stock.location"]
255
- pickings = self.env["stock.picking"].search(
256
- [
257
- ("picking_type_id", "in", self.picking_types.ids),
258
- ("state", "=", "assigned"),
259
- ("user_id", "in", (False, self.env.user.id)),
260
- ],
261
- order="user_id, priority desc, scheduled_date asc, id desc",
262
- )
237
+ move_lines = self.search_move_line.search_move_lines(match_user=True)
238
+ return first(move_lines).location_id
263
239
 
264
- for next_picking in pickings:
265
- move_lines = next_picking.move_line_ids.filtered(
266
- lambda line: line.qty_done < line.reserved_uom_qty
267
- )
268
- location = fields.first(move_lines).location_id
269
- if location:
270
- break
271
- return location
240
+ def _select_move_lines_first_location(self, move_lines):
241
+ location = first(move_lines).location_id
242
+ return move_lines.filtered(lambda line: line.location_id == location)
272
243
 
273
244
  def find_work(self):
274
245
  """Find the next location to work from, for a user.
@@ -285,14 +256,15 @@ class LocationContentTransfer(Component):
285
256
  response = self._recover_started_picking()
286
257
  if response:
287
258
  return response
259
+
288
260
  self._actions_for("lock").advisory(self._advisory_lock_find_work)
289
- location = self._find_location_to_work_from()
290
- if not location:
261
+ move_lines = self.search_move_line.search_move_lines(match_user=True)
262
+ if not move_lines:
291
263
  return self._response_for_start(message=self.msg_store.no_work_found())
292
- move_lines = self._find_location_move_lines(location)
264
+ move_lines = self._select_move_lines_first_location(move_lines)
293
265
  stock = self._actions_for("stock")
294
266
  stock.mark_move_line_as_picked(move_lines, quantity=0)
295
- return self._response_for_scan_location(location=location)
267
+ return self._response_for_scan_location(location=move_lines.location_id)
296
268
 
297
269
  def _find_move_lines_to_cancel_work(self, location):
298
270
  unreserve = self._actions_for("stock.unreserve")
@@ -356,7 +328,14 @@ class LocationContentTransfer(Component):
356
328
  message=self.msg_store.cannot_move_something_in_picking_type()
357
329
  )
358
330
 
359
- move_lines = self._find_location_move_lines_from_scan_location(location)
331
+ move_lines = self.search_move_line.search_move_lines(
332
+ locations=location,
333
+ match_user=True,
334
+ picking_type=self.env[
335
+ "stock.picking.type"
336
+ ], # disable filtering on picking types
337
+ )
338
+ move_lines = self._select_move_lines_first_location(move_lines)
360
339
 
361
340
  savepoint = self._actions_for("savepoint").new()
362
341
  unreserve = self._actions_for("stock.unreserve")
@@ -19,10 +19,13 @@ class ShopfloorMenu(Component):
19
19
  # TODO: maybe to be improved w/ raw SQL as this run for each menu item
20
20
  # and it's called every time the menu is opened/gets refreshed
21
21
  move_line_search = self._actions_for(
22
- "search_move_line", picking_types=record.picking_type_ids
22
+ "search_move_line",
23
+ picking_types=record.picking_type_ids,
24
+ additional_domain=record.move_line_search_additional_domain,
25
+ sort_order=record.move_line_search_sort_order,
26
+ sort_order_custom_code=record.move_line_search_sort_order_custom_code,
23
27
  )
24
- locations = record.picking_type_ids.mapped("default_location_src_id")
25
- lines_per_menu = move_line_search.search_move_lines_by_location(locations)
28
+ lines_per_menu = move_line_search.search_move_lines()
26
29
  return move_line_search.counters_for_lines(lines_per_menu)
27
30
 
28
31
  def _one_record_parser(self, record):
@@ -15,7 +15,15 @@ class BaseShopfloorService(AbstractComponent):
15
15
  @property
16
16
  def search_move_line(self):
17
17
  # TODO: propagating `picking_types` should probably be default
18
- return self._actions_for("search_move_line", propagate_kwargs=["picking_types"])
18
+ return self._actions_for(
19
+ "search_move_line",
20
+ propagate_kwargs=[
21
+ "picking_types",
22
+ "additional_domain",
23
+ "sort_order",
24
+ "sort_order_custom_code",
25
+ ],
26
+ )
19
27
 
20
28
 
21
29
  class BaseShopfloorProcess(AbstractComponent):
@@ -38,13 +46,31 @@ class BaseShopfloorProcess(AbstractComponent):
38
46
  )
39
47
  return self.work.picking_types
40
48
 
49
+ @property
50
+ def additional_domain(self):
51
+ return self.work.menu.move_line_search_additional_domain
52
+
53
+ @property
54
+ def sort_order(self):
55
+ return self.work.menu.move_line_search_sort_order
56
+
57
+ @property
58
+ def sort_order_custom_code(self):
59
+ return self.work.menu.move_line_search_sort_order_custom_code
60
+
41
61
  @property
42
62
  def search_move_line(self):
43
63
  # TODO: picking types should be set somehow straight in the work context
44
64
  # by `_validate_headers_update_work_context` in this way
45
65
  # we can remove this override and the need to call `_get_process_picking_types`
46
66
  # every time.
47
- return self._actions_for("search_move_line", picking_types=self.picking_types)
67
+ return self._actions_for(
68
+ "search_move_line",
69
+ picking_types=self.picking_types,
70
+ additional_domain=self.additional_domain,
71
+ sort_order=self.sort_order,
72
+ sort_order_custom_code=self.sort_order_custom_code,
73
+ )
48
74
 
49
75
  def _check_picking_status(self, pickings, states=("assigned",)):
50
76
  """Check if given pickings can be processed.
@@ -315,7 +315,7 @@ class ZonePicking(Component):
315
315
  return self.search_move_line.counters_for_lines(zone_lines)
316
316
 
317
317
  def _picking_type_zone_lines(self, zone_location, picking_type):
318
- return self.search_move_line.search_move_lines_by_location(
318
+ return self.search_move_line.search_move_lines(
319
319
  zone_location, picking_type=picking_type
320
320
  )
321
321
 
@@ -429,7 +429,7 @@ class ZonePicking(Component):
429
429
  enforce_empty_package=False,
430
430
  ):
431
431
  """Find lines that potentially need work in given locations."""
432
- return self.search_move_line.search_move_lines_by_location(
432
+ return self.search_move_line.search_move_lines(
433
433
  locations or self.zone_location,
434
434
  picking_type=picking_type or self.picking_type,
435
435
  order=self.lines_order,
@@ -367,7 +367,7 @@ ul.auto-toc {
367
367
  !! This file is generated by oca-gen-addon-readme !!
368
368
  !! changes will be overwritten. !!
369
369
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
370
- !! source digest: sha256:8c610188b1ca56f5f11da8e51e3fd390897186bc38eed9c6246f4cc394dccda1
370
+ !! source digest: sha256:bb5933746c40b25c45fcadac4704a1a752a86c48bb857969ca935196736d24c6
371
371
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
372
372
  <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/wms/tree/16.0/shopfloor"><img alt="OCA/wms" src="https://img.shields.io/badge/github-OCA%2Fwms-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/wms-16-0/wms-16-0-shopfloor"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/wms&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
373
373
  <p>Shopfloor is a barcode scanner application for internal warehouse operations.</p>
@@ -1,8 +1,10 @@
1
1
  from . import test_menu_counters
2
+ from . import test_menu_contrains
2
3
  from . import test_openapi
3
4
  from . import test_actions_change_package_lot
4
5
  from . import test_actions_data
5
6
  from . import test_actions_data_detail
7
+ from . import test_actions_search_move_line
6
8
  from . import test_actions_search
7
9
  from . import test_actions_stock
8
10
  from . import test_single_pack_transfer
@@ -0,0 +1,128 @@
1
+ # Copyright 2024 ACSONE SA/NV (http://www.acsone.eu)
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+ # pylint: disable=missing-return
4
+ from contextlib import contextmanager
5
+
6
+ from odoo.addons.base_rest.controllers.main import _PseudoCollection
7
+ from odoo.addons.component.core import WorkContext
8
+
9
+ from .common import CommonCase
10
+
11
+
12
+ class TestActionsSearchMoveLine(CommonCase):
13
+ @classmethod
14
+ def setUpClass(cls):
15
+ super().setUpClass()
16
+ cls.picking_user1 = cls._create_picking(
17
+ lines=[(cls.product_a, 10)], confirm=True
18
+ )
19
+ cls.picking_user2 = cls._create_picking(
20
+ lines=[(cls.product_a, 10)], confirm=True
21
+ )
22
+ cls.picking_no_user = cls._create_picking(
23
+ lines=[(cls.product_a, 10)], confirm=True
24
+ )
25
+
26
+ cls.pickings = cls.picking_no_user | cls.picking_user1 | cls.picking_user2
27
+ cls._fill_stock_for_moves(cls.pickings.move_ids)
28
+ cls.pickings.action_assign()
29
+ cls.move_lines = cls.pickings.move_line_ids
30
+ cls.user1 = cls.env.user.copy({"name": "User 1", "login": "user1"})
31
+ cls.user2 = cls.env.user.copy({"name": "User 2", "login": "user2"})
32
+ cls.picking_no_user.user_id = False
33
+ cls.picking_user1.user_id = cls.user1.id
34
+ cls.picking_user2.move_line_ids.write({"shopfloor_user_id": cls.user2.id})
35
+ cls.picking_user2.user_id = cls.user2.id
36
+
37
+ @contextmanager
38
+ def work_on_actions(self, user=None, **params):
39
+ params = params or {}
40
+ env = self.env(user=user) if user else self.env
41
+ collection = _PseudoCollection("shopfloor.action", env)
42
+ yield WorkContext(
43
+ model_name="rest.service.registration", collection=collection, **params
44
+ )
45
+
46
+ @contextmanager
47
+ def search_move_line(self, user=None):
48
+ with self.work_on_actions(user) as work:
49
+ move_line_search = work.component(usage="search_move_line")
50
+ yield move_line_search
51
+
52
+ @classmethod
53
+ def setUpClassVars(cls):
54
+ super().setUpClassVars()
55
+ cls.wh = cls.env.ref("stock.warehouse0")
56
+ cls.picking_type = cls.wh.out_type_id
57
+
58
+ def test_search_move_line_sorter(self):
59
+ with self.search_move_line() as move_line_search:
60
+ move_lines = self.move_lines.sorted(
61
+ move_line_search._sort_key_move_lines(order="assigned_to_current_user")
62
+ )
63
+ # we must get operations in the following order:
64
+ # * no shopfloor user and not user assigned to picking
65
+ # * no shopfloor user and user assigned to picking
66
+ # * shopfloor user or user assigned to picking
67
+
68
+ self.assertFalse(move_lines[0].shopfloor_user_id)
69
+ self.assertFalse(move_lines[0].picking_id.user_id)
70
+ self.assertFalse(move_lines[1].shopfloor_user_id)
71
+ self.assertTrue(move_lines[1].picking_id.user_id)
72
+ self.assertTrue(move_lines[2].shopfloor_user_id)
73
+
74
+ with self.search_move_line(user=self.user1) as move_line_search:
75
+ move_lines = self.move_lines.sorted(
76
+ move_line_search._sort_key_move_lines(order="assigned_to_current_user")
77
+ )
78
+ # user1 is only assigned at picking level
79
+ # we must get operations in the following order:
80
+ # * no shopfloor user but user assigned to picking
81
+ # * no shopfloor user and not user assigned to picking
82
+ # * shopfloor user or user assigned to picking
83
+ self.assertFalse(move_lines[0].shopfloor_user_id)
84
+ self.assertEqual(move_lines[0].picking_id.user_id, self.user1)
85
+ self.assertFalse(move_lines[1].shopfloor_user_id)
86
+ self.assertTrue(move_lines[2].shopfloor_user_id)
87
+
88
+ with self.search_move_line(user=self.user2) as move_line_search:
89
+ move_lines = self.move_lines.sorted(
90
+ move_line_search._sort_key_move_lines(order="assigned_to_current_user")
91
+ )
92
+ # user2 is only assigned at move level
93
+ # we must get operations in the following order:
94
+ # * shopfloor user or user assigned to picking
95
+ # * no shopfloor user and no user assigned to picking
96
+ # * no shopfloor user and user assigned to picking
97
+ self.assertEqual(move_lines[0].shopfloor_user_id, self.user2)
98
+ self.assertFalse(move_lines[1].shopfloor_user_id)
99
+ self.assertFalse(move_lines[1].picking_id.user_id)
100
+ self.assertTrue(move_lines[2].picking_id.user_id)
101
+
102
+ def test_search_move_line_match_user(self):
103
+ with self.search_move_line() as move_line_search:
104
+ move_lines = move_line_search.search_move_lines(
105
+ locations=self.picking_type.default_location_src_id,
106
+ match_user=True,
107
+ )
108
+ # we must only get picking not assigned to a user
109
+ self.assertFalse(move_lines.picking_id.user_id)
110
+ self.assertFalse(move_lines.shopfloor_user_id)
111
+
112
+ with self.search_move_line(user=self.user1) as move_line_search:
113
+ move_lines = move_line_search.search_move_lines(
114
+ locations=self.picking_type.default_location_src_id,
115
+ match_user=True,
116
+ )
117
+ # we must only get picking assigned to user1 or not assigned to any user
118
+ self.assertEqual(move_lines.picking_id.user_id, self.user1)
119
+ self.assertFalse(move_lines.shopfloor_user_id)
120
+
121
+ with self.search_move_line(user=self.user2) as move_line_search:
122
+ move_lines = move_line_search.search_move_lines(
123
+ locations=self.picking_type.default_location_src_id,
124
+ match_user=True,
125
+ )
126
+ # we must only get picking assigned to user2
127
+ self.assertEqual(move_lines.picking_id.user_id, self.user2)
128
+ self.assertEqual(move_lines.shopfloor_user_id, self.user2)