odoo-addon-shopfloor 16.0.2.2.0__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 (23) 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/location_content_transfer.py +20 -41
  9. odoo/addons/shopfloor/services/menu.py +6 -3
  10. odoo/addons/shopfloor/services/service.py +28 -2
  11. odoo/addons/shopfloor/services/zone_picking.py +2 -2
  12. odoo/addons/shopfloor/static/description/index.html +1 -1
  13. odoo/addons/shopfloor/tests/__init__.py +2 -0
  14. odoo/addons/shopfloor/tests/test_actions_search_move_line.py +128 -0
  15. odoo/addons/shopfloor/tests/test_location_content_transfer_get_work.py +41 -0
  16. odoo/addons/shopfloor/tests/test_location_content_transfer_mix.py +2 -2
  17. odoo/addons/shopfloor/tests/test_menu_contrains.py +40 -0
  18. odoo/addons/shopfloor/tests/test_menu_counters.py +112 -2
  19. odoo/addons/shopfloor/views/shopfloor_menu.xml +54 -0
  20. {odoo_addon_shopfloor-16.0.2.2.0.dist-info → odoo_addon_shopfloor-16.0.2.2.1.4.dist-info}/METADATA +2 -2
  21. {odoo_addon_shopfloor-16.0.2.2.0.dist-info → odoo_addon_shopfloor-16.0.2.2.1.4.dist-info}/RECORD +23 -20
  22. {odoo_addon_shopfloor-16.0.2.2.0.dist-info → odoo_addon_shopfloor-16.0.2.2.1.4.dist-info}/WHEEL +0 -0
  23. {odoo_addon_shopfloor-16.0.2.2.0.dist-info → odoo_addon_shopfloor-16.0.2.2.1.4.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -105,6 +105,47 @@ class TestLocationContentTransferGetWork(LocationContentTransferCommonCase):
105
105
  )
106
106
  self.assertEqual(response["next_state"], "scan_destination_all")
107
107
 
108
+ def test_find_work_additional_domain(self):
109
+ next_location = self.service._find_location_to_work_from()
110
+ self.assertTrue(next_location)
111
+ self.menu.sudo().move_line_search_additional_domain = [
112
+ ("picking_id.id", "not in", self.pickings.ids)
113
+ ]
114
+ next_location = self.service._find_location_to_work_from()
115
+ self.assertFalse(next_location)
116
+ response = self.service.dispatch("find_work", params={})
117
+ self.assert_response(
118
+ response,
119
+ next_state="get_work",
120
+ data={},
121
+ message=self.service.msg_store.no_work_found(),
122
+ )
123
+
124
+ def test_find_work_custom_sort_key(self):
125
+ # fmt: off
126
+ self.menu.sudo().write(
127
+ {
128
+ "move_line_search_sort_order": "custom_code",
129
+ "move_line_search_sort_order_custom_code":
130
+ f"key = (-1 if line.location_id.id == {self.content_loc.id} else 10, )",
131
+ }
132
+ )
133
+ # fmt: on
134
+
135
+ next_location = self.service._find_location_to_work_from()
136
+ self.assertEqual(next_location, self.content_loc)
137
+ self.menu.sudo().move_line_search_sort_order_custom_code = (
138
+ f"key = (-1 if line.location_id.id == {self.content_loc2.id} else 10, )"
139
+ )
140
+ next_location = self.service._find_location_to_work_from()
141
+ self.assertEqual(next_location, self.content_loc2)
142
+ self.menu.sudo().move_line_search_sort_order_custom_code = f"""
143
+ key = (-1 if line.location_id.id == {self.content_loc2.id} else 10, ) + \
144
+ get_sort_key_assigned_to_current_user(line)
145
+ """
146
+ next_location = self.service._find_location_to_work_from()
147
+ self.assertEqual(next_location, self.content_loc2)
148
+
108
149
  def test_cancel_work(self):
109
150
  next_location = self.service._find_location_to_work_from()
110
151
  stock = self.service._actions_for("stock")
@@ -370,8 +370,8 @@ class LocationContentTransferMixCase(LocationContentTransferCommonCase):
370
370
  lambda l: not l.shopfloor_user_id and l.location_id == dest_location2
371
371
  )
372
372
  picking_before = pack_second_pallet.picking_id
373
- move_lines = self.service._find_location_move_lines(
374
- pack_second_pallet.location_id
373
+ move_lines = self.service.search_move_line.search_move_lines(
374
+ locations=pack_second_pallet.location_id
375
375
  )
376
376
  response = self._location_content_transfer_process_line(pack_second_pallet)
377
377
  response_packages = response["data"]["scan_destination_all"]["package_levels"]
@@ -0,0 +1,40 @@
1
+ # Copyright 2024 ACSONE SA/NV
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+ from odoo import exceptions
4
+
5
+ from .test_menu_base import MenuCountersCommonCase
6
+
7
+
8
+ class TestMenuContrains(MenuCountersCommonCase):
9
+ def test_move_line_search_sort_order_custom_code_invalid(self):
10
+
11
+ with self.assertRaises(exceptions.ValidationError):
12
+ # wrong indentation in python code
13
+ self.menu1.sudo().write(
14
+ {
15
+ "move_line_search_sort_order_custom_code": """
16
+ key = 1
17
+ toto(1)
18
+ """,
19
+ "move_line_search_sort_order": "custom_code",
20
+ }
21
+ )
22
+
23
+ self.menu1.sudo().write(
24
+ {
25
+ "move_line_search_sort_order_custom_code": "key = 1",
26
+ "move_line_search_sort_order": "custom_code",
27
+ }
28
+ )
29
+
30
+ def test_move_line_search_sort_order_mismatch(self):
31
+ with self.assertRaises(exceptions.ValidationError):
32
+ self.menu1.sudo().move_line_search_sort_order = "custom_code"
33
+
34
+ self.menu1.sudo().write(
35
+ {
36
+ "move_line_search_sort_order_custom_code": "key = 1",
37
+ "move_line_search_sort_order": "priority",
38
+ }
39
+ )
40
+ self.assertFalse(self.menu1.move_line_search_sort_order_custom_code)
@@ -24,6 +24,9 @@ class TestMenuCountersCommonCase(MenuCountersCommonCase):
24
24
  .sudo()
25
25
  .search([("profile_id", "=", self.wms_profile.id)])
26
26
  )
27
+ # ensures expected counters are in the expected menu items
28
+ self.assertIn(self.menu1, expected_menu_items)
29
+ self.assertIn(self.menu2, expected_menu_items)
27
30
  service = self.get_service("menu", profile=self.wms_profile)
28
31
  response = service.dispatch("search")
29
32
  self._assert_menu_response(
@@ -33,6 +36,51 @@ class TestMenuCountersCommonCase(MenuCountersCommonCase):
33
36
  )
34
37
 
35
38
  def test_menu_search_profile2(self):
39
+ expected_menu_items = (
40
+ self.env["shopfloor.menu"]
41
+ .sudo()
42
+ .search([("profile_id", "=", self.wms_profile2.id)])
43
+ )
44
+ expected_counters = {
45
+ expected_menu_item[0].id: {
46
+ "lines_count": 0,
47
+ "picking_count": 0,
48
+ "priority_lines_count": 0,
49
+ "priority_picking_count": 0,
50
+ }
51
+ for expected_menu_item in expected_menu_items
52
+ }
53
+ service = self.get_service("menu", profile=self.wms_profile2)
54
+ response = service.dispatch("search")
55
+ self._assert_menu_response(
56
+ response,
57
+ expected_menu_items.sorted("sequence"),
58
+ expected_counters=expected_counters,
59
+ )
60
+
61
+ def test_menu_counter_priority(self):
62
+ """Test that priority counters are correctly computed.
63
+
64
+ Priority on lines must be based on the move priority.
65
+ Priority on pickings must be based on the picking priority.
66
+ """
67
+ self.pickings.write({"priority": "0"})
68
+ self.pickings.move_ids.write({"priority": "0"})
69
+ pickings_menu_2 = self.pickings.filtered(
70
+ lambda p: p.picking_type_id == self.menu2_picking_type
71
+ )
72
+ # Set priority on pickings only for first picking of menu 2
73
+ pickings_menu_2[0].write({"priority": "1"})
74
+ pickings_menu_2[0].move_ids.write({"priority": "0"})
75
+
76
+ # set priority on move lines only for second picking of menu 2
77
+ pickings_menu_2[1].write({"priority": "0"})
78
+ pickings_menu_2[1].move_ids[0].write({"priority": "1"})
79
+
80
+ # set priority on both move lines and pickings for third picking of menu 2
81
+ pickings_menu_2[2].write({"priority": "1"})
82
+ pickings_menu_2[2].move_ids.write({"priority": "1"})
83
+
36
84
  expected_counters = {
37
85
  self.menu1.id: {
38
86
  "lines_count": 2,
@@ -40,6 +88,41 @@ class TestMenuCountersCommonCase(MenuCountersCommonCase):
40
88
  "priority_lines_count": 0,
41
89
  "priority_picking_count": 0,
42
90
  },
91
+ self.menu2.id: {
92
+ "lines_count": 6,
93
+ "picking_count": 3,
94
+ "priority_lines_count": 1 + len(pickings_menu_2[2].move_ids),
95
+ "priority_picking_count": 2,
96
+ },
97
+ }
98
+ expected_menu_items = (
99
+ self.env["shopfloor.menu"]
100
+ .sudo()
101
+ .search([("profile_id", "=", self.wms_profile.id)])
102
+ )
103
+ # ensures expected counters are in the expected menu items
104
+ self.assertIn(self.menu1, expected_menu_items)
105
+ self.assertIn(self.menu2, expected_menu_items)
106
+ service = self.get_service("menu", profile=self.wms_profile)
107
+ response = service.dispatch("search")
108
+ self._assert_menu_response(
109
+ response,
110
+ expected_menu_items.sorted("sequence"),
111
+ expected_counters=expected_counters,
112
+ )
113
+
114
+ def test_menu_search_additional_domain(self):
115
+ self.menu1.sudo().move_line_search_additional_domain = [
116
+ ("picking_id.priority", "=", "1")
117
+ ]
118
+
119
+ expected_counters = {
120
+ self.menu1.id: {
121
+ "lines_count": 0,
122
+ "picking_count": 0,
123
+ "priority_lines_count": 0,
124
+ "priority_picking_count": 0,
125
+ },
43
126
  self.menu2.id: {
44
127
  "lines_count": 6,
45
128
  "picking_count": 3,
@@ -50,9 +133,36 @@ class TestMenuCountersCommonCase(MenuCountersCommonCase):
50
133
  expected_menu_items = (
51
134
  self.env["shopfloor.menu"]
52
135
  .sudo()
53
- .search([("profile_id", "=", self.wms_profile2.id)])
136
+ .search([("profile_id", "=", self.wms_profile.id)])
54
137
  )
55
- service = self.get_service("menu", profile=self.wms_profile2)
138
+ # ensures expected counters are in the expected menu items
139
+ self.assertIn(self.menu1, expected_menu_items)
140
+ self.assertIn(self.menu2, expected_menu_items)
141
+ service = self.get_service("menu", profile=self.wms_profile)
142
+ response = service.dispatch("search")
143
+ self._assert_menu_response(
144
+ response,
145
+ expected_menu_items.sorted("sequence"),
146
+ expected_counters=expected_counters,
147
+ )
148
+
149
+ self.menu1.sudo().move_line_search_additional_domain = [
150
+ ("picking_id.priority", "=", "0")
151
+ ]
152
+ expected_counters = {
153
+ self.menu1.id: {
154
+ "lines_count": 2,
155
+ "picking_count": 2,
156
+ "priority_lines_count": 0,
157
+ "priority_picking_count": 0,
158
+ },
159
+ self.menu2.id: {
160
+ "lines_count": 6,
161
+ "picking_count": 3,
162
+ "priority_lines_count": 0,
163
+ "priority_picking_count": 0,
164
+ },
165
+ }
56
166
  response = service.dispatch("search")
57
167
  self._assert_menu_response(
58
168
  response,
@@ -169,6 +169,60 @@
169
169
  />
170
170
  <field name="allow_alternative_destination_package" />
171
171
  </group>
172
+ <group
173
+ name="search_move_line"
174
+ attrs="{'invisible': [('move_line_search_sort_order_is_possible', '=', False), ('move_line_search_additional_domain_is_possible', '=', False)]}"
175
+ >
176
+ <field
177
+ name="move_line_search_additional_domain_is_possible"
178
+ invisible="1"
179
+ />
180
+ <field
181
+ name="move_line_search_additional_domain"
182
+ widget="domain"
183
+ options="{'model': 'stock.move.line'}"
184
+ attrs="{'invisible': [('move_line_search_additional_domain_is_possible', '=', False)]}"
185
+ />
186
+ <field name="move_line_search_sort_order_is_possible" invisible="1" />
187
+ <field
188
+ name="move_line_search_sort_order"
189
+ attrs="{'invisible': [('move_line_search_sort_order_is_possible', '=', False)]}"
190
+ />
191
+ <div
192
+ class="o_cell o_wrap_label"
193
+ style="margin-top: 4px;"
194
+ colspan="2"
195
+ attrs="{'invisible': ['|', ('move_line_search_sort_order_is_possible', '=', False), ('move_line_search_sort_order', '!=', 'custom_code')]}"
196
+ >
197
+ <label
198
+ class="text-900"
199
+ for="move_line_search_sort_order_custom_code"
200
+ >Custom code to construct the key to sort move lines</label>
201
+ <p>Three variables are available:</p>
202
+ <ul>
203
+ <li><code
204
+ >line</code>: The move line for with the key must be computed.
205
+ </li>
206
+ <li><code
207
+ >get_sort_key_priority</code>: Return sort key to sort by priority. (return a tuple)
208
+ </li>
209
+ <li><code
210
+ >get_sort_key_location</code>: Return sort key to sort by location. (return a tuple)
211
+ </li>
212
+ <li><code
213
+ >get_sort_key_assigned_to_current_user</code>: Return sort key to get line assigned to current user first. (return a tuple)
214
+ </li>
215
+ <li>To assign the key value: <code
216
+ >key = get_sort_key_assigned_to_current_user(line) + (line.x, )</code></li>
217
+ </ul>
218
+ <field
219
+ name="move_line_search_sort_order_custom_code"
220
+ attrs="{'invisible': ['|', ('move_line_search_sort_order_is_possible', '=', False), ('move_line_search_sort_order', '!=', 'custom_code')]}"
221
+ nolabel="1"
222
+ />
223
+ </div>
224
+
225
+ </group>
172
226
  </group>
173
227
  </field>
174
228
  </record>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: odoo-addon-shopfloor
3
- Version: 16.0.2.2.0
3
+ Version: 16.0.2.2.1.4
4
4
  Summary: manage warehouse operations with barcode scanners
5
5
  Home-page: https://github.com/OCA/wms
6
6
  Author: Camptocamp, BCIM, Akretion, Odoo Community Association (OCA)
@@ -36,7 +36,7 @@ Shopfloor
36
36
  !! This file is generated by oca-gen-addon-readme !!
37
37
  !! changes will be overwritten. !!
38
38
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
39
- !! source digest: sha256:8c610188b1ca56f5f11da8e51e3fd390897186bc38eed9c6246f4cc394dccda1
39
+ !! source digest: sha256:bb5933746c40b25c45fcadac4704a1a752a86c48bb857969ca935196736d24c6
40
40
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
41
41
 
42
42
  .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png