odoo-addon-shopfloor 16.0.2.1.2__py3-none-any.whl → 16.0.2.2.1.4__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 (24) 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/shopfloor.pot +102 -0
  6. odoo/addons/shopfloor/migrations/16.0.2.2.1/post-init_search_move_line_options.py +33 -0
  7. odoo/addons/shopfloor/models/shopfloor_menu.py +85 -0
  8. odoo/addons/shopfloor/services/cluster_picking.py +1 -2
  9. odoo/addons/shopfloor/services/location_content_transfer.py +20 -41
  10. odoo/addons/shopfloor/services/menu.py +6 -3
  11. odoo/addons/shopfloor/services/service.py +28 -2
  12. odoo/addons/shopfloor/services/zone_picking.py +2 -2
  13. odoo/addons/shopfloor/static/description/index.html +1 -1
  14. odoo/addons/shopfloor/tests/__init__.py +2 -0
  15. odoo/addons/shopfloor/tests/test_actions_search_move_line.py +128 -0
  16. odoo/addons/shopfloor/tests/test_location_content_transfer_get_work.py +41 -0
  17. odoo/addons/shopfloor/tests/test_location_content_transfer_mix.py +2 -2
  18. odoo/addons/shopfloor/tests/test_menu_contrains.py +40 -0
  19. odoo/addons/shopfloor/tests/test_menu_counters.py +112 -2
  20. odoo/addons/shopfloor/views/shopfloor_menu.xml +54 -0
  21. {odoo_addon_shopfloor-16.0.2.1.2.dist-info → odoo_addon_shopfloor-16.0.2.2.1.4.dist-info}/METADATA +2 -2
  22. {odoo_addon_shopfloor-16.0.2.1.2.dist-info → odoo_addon_shopfloor-16.0.2.2.1.4.dist-info}/RECORD +24 -21
  23. {odoo_addon_shopfloor-16.0.2.1.2.dist-info → odoo_addon_shopfloor-16.0.2.2.1.4.dist-info}/WHEEL +0 -0
  24. {odoo_addon_shopfloor-16.0.2.1.2.dist-info → odoo_addon_shopfloor-16.0.2.2.1.4.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ Shopfloor
7
7
  !! This file is generated by oca-gen-addon-readme !!
8
8
  !! changes will be overwritten. !!
9
9
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10
- !! source digest: sha256:aae7d17842cb10c62e1c8d1e78a7045c4dffad614b83e507a5279e30126670ca
10
+ !! source digest: sha256:bb5933746c40b25c45fcadac4704a1a752a86c48bb857969ca935196736d24c6
11
11
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12
12
 
13
13
  .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "name": "Shopfloor",
8
8
  "summary": "manage warehouse operations with barcode scanners",
9
- "version": "16.0.2.1.2",
9
+ "version": "16.0.2.2.1",
10
10
  "development_status": "Beta",
11
11
  "category": "Inventory",
12
12
  "website": "https://github.com/OCA/wms",
@@ -1,5 +1,8 @@
1
1
  # Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2
+ # Copyright 2024 ACSONE SA/NV
2
3
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
4
+ from odoo.tools import safe_eval
5
+
3
6
  from odoo.addons.component.core import Component
4
7
 
5
8
 
@@ -20,9 +23,42 @@ class MoveLineSearch(Component):
20
23
  self.work, "picking_types", self.env["stock.picking.type"].browse()
21
24
  )
22
25
 
23
- def _search_move_lines_by_location_domain(
26
+ @property
27
+ def additional_domain(self):
28
+ return getattr(self.work, "additional_domain", [])
29
+
30
+ @property
31
+ def sort_order(self):
32
+ return getattr(self.work, "sort_order", "priority")
33
+
34
+ @property
35
+ def sort_order_custom_code(self):
36
+ return getattr(self.work, "sort_order_custom_code", None)
37
+
38
+ def _get_additional_domain_eval_context(self):
39
+ """Prepare the context used when evaluating the additional domain
40
+ :returns: dict -- evaluation context given to safe_eval
41
+ """
42
+ return {
43
+ "datetime": safe_eval.datetime,
44
+ "dateutil": safe_eval.dateutil,
45
+ "time": safe_eval.time,
46
+ "uid": self.env.uid,
47
+ "user": self.env.user,
48
+ }
49
+
50
+ def _sort_key_custom_code_eval_context(self, line):
51
+ return {
52
+ "line": line,
53
+ "key": None,
54
+ "get_sort_key_priority": self._sort_key_move_lines_priority,
55
+ "get_sort_key_location": self._sort_key_move_lines_location,
56
+ "get_sort_key_assigned_to_current_user": self._sort_key_assigned_to_current_user,
57
+ }
58
+
59
+ def _search_move_lines_domain(
24
60
  self,
25
- locations,
61
+ locations=None,
26
62
  picking_type=None,
27
63
  package=None,
28
64
  product=None,
@@ -32,16 +68,26 @@ class MoveLineSearch(Component):
32
68
  # When True, adds the package in the domain even if the package is False
33
69
  enforce_empty_package=False,
34
70
  ):
71
+ """Return a domain to search move lines.
72
+
73
+ Be careful on the use of the picking_type parameter. This paramater can take
74
+ a recordset or None as value. The interpretation of the value is as follows:
75
+ * If picking_type is None, the domain will be filtered on all picking types
76
+ defined in the work. (most probably those defined on the menu)
77
+ * If picking_type is a recordset, the domain will be filtered on the given
78
+ picking types if the recordset is not empty. If the recordset is empty,
79
+ the domain will not be filtered on any picking type.
80
+ """
35
81
  domain = [
36
- ("location_id", "child_of", locations.ids),
37
82
  ("qty_done", "=", 0),
38
83
  ("state", "in", ("assigned", "partially_available")),
39
84
  ]
40
- if picking_type:
41
- # auto_join in place for this field
42
- domain += [("picking_id.picking_type_id", "=", picking_type.id)]
43
- elif self.picking_types:
44
- domain += [("picking_id.picking_type_id", "in", self.picking_types.ids)]
85
+ picking_types = picking_type if picking_type is not None else self.picking_types
86
+ if picking_types:
87
+ domain += [("picking_id.picking_type_id", "in", picking_types.ids)]
88
+ locations = locations or picking_types.default_location_src_id
89
+ if locations:
90
+ domain += [("location_id", "child_of", locations.ids)]
45
91
  if package or package is not None and enforce_empty_package:
46
92
  domain += [("package_id", "=", package.id if package else False)]
47
93
  if product:
@@ -49,23 +95,26 @@ class MoveLineSearch(Component):
49
95
  if lot:
50
96
  domain += [("lot_id", "=", lot.id)]
51
97
  if match_user:
98
+ # we only want to see the lines assigned to the current user
52
99
  domain += [
53
- "|",
54
- ("shopfloor_user_id", "=", False),
55
- ("shopfloor_user_id", "=", self.env.uid),
100
+ ("shopfloor_user_id", "in", (False, self.env.uid)),
101
+ ("picking_id.user_id", "in", (False, self.env.uid)),
56
102
  ]
57
103
  if picking_ready:
58
104
  domain += [("picking_id.state", "=", "assigned")]
105
+ if self.additional_domain:
106
+ eval_context = self._get_additional_domain_eval_context()
107
+ domain += safe_eval.safe_eval(self.additional_domain, eval_context)
59
108
  return domain
60
109
 
61
- def search_move_lines_by_location(
110
+ def search_move_lines(
62
111
  self,
63
- locations,
112
+ locations=None,
64
113
  picking_type=None,
65
114
  package=None,
66
115
  product=None,
67
116
  lot=None,
68
- order="priority",
117
+ order=None,
69
118
  match_user=False,
70
119
  sort_keys_func=None,
71
120
  picking_ready=True,
@@ -73,7 +122,7 @@ class MoveLineSearch(Component):
73
122
  ):
74
123
  """Find lines that potentially need work in given locations."""
75
124
  move_lines = self.env["stock.move.line"].search(
76
- self._search_move_lines_by_location_domain(
125
+ self._search_move_lines_domain(
77
126
  locations,
78
127
  picking_type,
79
128
  package,
@@ -84,36 +133,79 @@ class MoveLineSearch(Component):
84
133
  enforce_empty_package=enforce_empty_package,
85
134
  )
86
135
  )
136
+ order = order or self.sort_order
87
137
  sort_keys_func = sort_keys_func or self._sort_key_move_lines(order)
88
138
  move_lines = move_lines.sorted(sort_keys_func)
89
139
  return move_lines
90
140
 
91
- @staticmethod
92
- def _sort_key_move_lines(order):
141
+ def _sort_key_move_lines(self, order=None):
93
142
  """Return a sorting function to order lines."""
143
+ if order is None:
144
+ return lambda line: tuple()
94
145
 
95
146
  if order == "priority":
96
- # make prority negative to keep sorting ascending
97
- return lambda line: (
98
- -int(line.move_id.priority or "0"),
99
- line.move_id.date,
100
- line.move_id.id,
101
- )
102
- elif order == "location":
103
- return lambda line: (
104
- line.location_id.shopfloor_picking_sequence or "",
105
- line.location_id.name,
106
- line.move_id.date,
107
- line.move_id.id,
108
- )
109
- return lambda line: line
147
+ return self._sort_key_move_lines_priority
148
+
149
+ if order == "location":
150
+ return self._sort_key_move_lines_location
151
+
152
+ if order == "custom_code":
153
+ return self._sort_key_custom_code
154
+
155
+ if order == "assigned_to_current_user":
156
+ return self._sort_key_assigned_to_current_user
157
+
158
+ raise ValueError(f"Unknown order '{order}'")
159
+
160
+ def _sort_key_move_lines_priority(self, line):
161
+ # make prority negative to keep sorting ascending
162
+ return self._sort_key_assigned_to_current_user(line) + (
163
+ -int(line.move_id.priority or "0"),
164
+ line.move_id.date,
165
+ line.move_id.id,
166
+ )
167
+
168
+ def _sort_key_move_lines_location(self, line):
169
+ return self._sort_key_assigned_to_current_user(line) + (
170
+ line.location_id.shopfloor_picking_sequence or "",
171
+ line.location_id.name,
172
+ line.move_id.date,
173
+ line.move_id.id,
174
+ )
175
+
176
+ def _sort_key_assigned_to_current_user(self, line):
177
+ user_id = line.shopfloor_user_id.id or line.picking_id.user_id.id or None
178
+ # Determine sort priority
179
+ # Priority 0: Assigned to the current user
180
+ # Priority 1: Not assigned to any user
181
+ # Priority 2: Assigned to a different user
182
+ if user_id == self.env.uid:
183
+ priority = 0
184
+ elif user_id is None:
185
+ priority = 1
186
+ else:
187
+ priority = 2
188
+ return (priority,)
189
+
190
+ def _sort_key_custom_code(self, line):
191
+ context = self._sort_key_custom_code_eval_context(line)
192
+ safe_eval.safe_eval(
193
+ self.sort_order_custom_code, context, mode="exec", nocopy=True
194
+ )
195
+ return context["key"]
110
196
 
111
197
  def counters_for_lines(self, lines):
112
198
  # Not using mapped/filtered to support simple lists and generators
113
- priority_lines = [x for x in lines if int(x.picking_id.priority or "0") > 0]
199
+ priority_lines = set()
200
+ priority_pickings = set()
201
+ for line in lines:
202
+ if int(line.move_id.priority or "0") > 0:
203
+ priority_lines.add(line)
204
+ if int(line.picking_id.priority or "0") > 0:
205
+ priority_pickings.add(line.picking_id)
114
206
  return {
115
207
  "lines_count": len(lines),
116
208
  "picking_count": len({x.picking_id.id for x in lines}),
117
209
  "priority_lines_count": len(priority_lines),
118
- "priority_picking_count": len({x.picking_id.id for x in priority_lines}),
210
+ "priority_picking_count": len(priority_pickings),
119
211
  }
@@ -21,7 +21,9 @@
21
21
  "multiple_move_single_pack": true,
22
22
  "no_prefill_qty": true,
23
23
  "scan_location_or_pack_first": true,
24
- "allow_alternative_destination_package": true
24
+ "allow_alternative_destination_package": true,
25
+ "allow_move_line_search_sort_order": false,
26
+ "allow_move_line_search_additional_domain": true
25
27
  }
26
28
  </field>
27
29
  </record>
@@ -68,7 +70,10 @@
68
70
  "allow_unreserve_other_moves": true,
69
71
  "allow_ignore_no_putaway_available": true,
70
72
  "allow_get_work": true,
71
- "no_prefill_qty": true
73
+ "no_prefill_qty": true,
74
+ "allow_move_line_search_sort_order": true,
75
+ "allow_move_line_search_additional_domain": true
76
+
72
77
  }
73
78
  </field>
74
79
  </record>
@@ -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)
@@ -339,8 +339,7 @@ class ClusterPicking(Component):
339
339
  return self.prepare_unload(batch.id)
340
340
  return self._response_for_start_line(next_line, message=message)
341
341
 
342
- @staticmethod
343
- def _sort_key_lines(line):
342
+ def _sort_key_lines(self, line):
344
343
  return (
345
344
  line.shopfloor_priority or 10,
346
345
  line.location_id.shopfloor_picking_sequence or "",
@@ -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")