Skip to content

Commit eed1335

Browse files
committed
Merge PR #902 into 16.0
Signed-off-by rousseldenis
2 parents 0f76e45 + d84f2d0 commit eed1335

10 files changed

+213
-55
lines changed

shopfloor/__manifest__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{
77
"name": "Shopfloor",
88
"summary": "manage warehouse operations with barcode scanners",
9-
"version": "16.0.2.4.1",
9+
"version": "16.0.2.4.2",
1010
"development_status": "Beta",
1111
"category": "Inventory",
1212
"website": "https://github.com/OCA/wms",

shopfloor/data/shopfloor_scenario_data.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"scan_location_or_pack_first": true,
2424
"allow_alternative_destination_package": true,
2525
"allow_move_line_search_sort_order": false,
26-
"allow_move_line_search_additional_domain": true
26+
"allow_move_line_search_additional_domain": true,
27+
"require_destination_package": true
2728
}
2829
</field>
2930
</record>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright 2024 ACSONE SA/NV
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+
_logger.info("Updating scenario Zone Picking")
14+
if not version:
15+
return
16+
env = api.Environment(cr, SUPERUSER_ID, {})
17+
zone_picking_scenario = env.ref("shopfloor.scenario_zone_picking")
18+
_update_scenario_options(zone_picking_scenario)
19+
20+
21+
def _update_scenario_options(scenario):
22+
options = scenario.options
23+
if "require_destination_package" not in options:
24+
options["require_destination_package"] = True
25+
options_edit = json.dumps(options or {}, indent=4, sort_keys=True)
26+
scenario.write({"options_edit": options_edit})
27+
_logger.info(
28+
"Option require_destination_package added to scenario Zone Picking"
29+
)

shopfloor/models/shopfloor_menu.py

+41-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,6 @@ class ShopfloorMenu(models.Model):
228228
allow_alternative_destination_package_is_possible = fields.Boolean(
229229
compute="_compute_allow_alternative_destination_package_is_possible"
230230
)
231-
232231
move_line_search_additional_domain_is_possible = fields.Boolean(
233232
compute="_compute_move_line_search_additional_domain_is_possible"
234233
)
@@ -250,6 +249,16 @@ class ShopfloorMenu(models.Model):
250249
move_line_search_sort_order_custom_code = fields.Text(
251250
string="Custom sort key code", help="Python code to sort move lines. "
252251
)
252+
require_destination_package = fields.Boolean(
253+
string="Destination package required",
254+
default=True,
255+
help="If set, the user will have to scan only the source location "
256+
"and the destination location to process a line. The unload step will be skipped.",
257+
)
258+
259+
require_destination_package_is_possible = fields.Boolean(
260+
compute="_compute_require_destination_package_is_possible"
261+
)
253262

254263
@api.onchange("unload_package_at_destination")
255264
def _onchange_unload_package_at_destination(self):
@@ -267,6 +276,16 @@ def _onchange_pick_pack_same_time(self):
267276
record.unload_package_at_destination = False
268277
record.multiple_move_single_pack = False
269278

279+
@api.onchange("require_destination_package")
280+
def _onchange_require_destination_package(self):
281+
# require_destination_package is incompatible with pick_pack_same_time and
282+
# unload_package_at_destination and multiple_move_single_pack
283+
for record in self:
284+
if not record.require_destination_package:
285+
record.pick_pack_same_time = False
286+
record.unload_package_at_destination = False
287+
record.multiple_move_single_pack = False
288+
270289
@api.onchange("multiple_move_single_pack")
271290
def _onchange_multiple_move_single_pack(self):
272291
# multiple_move_single_pack is incompatible with pick_pack_same_time,
@@ -278,6 +297,7 @@ def _onchange_multiple_move_single_pack(self):
278297
"unload_package_at_destination",
279298
"pick_pack_same_time",
280299
"multiple_move_single_pack",
300+
"require_destination_package",
281301
)
282302
def _check_options(self):
283303
if self.pick_pack_same_time and self.unload_package_at_destination:
@@ -294,6 +314,19 @@ def _check_options(self):
294314
"'Multiple moves same destination package'."
295315
)
296316
)
317+
elif not self.require_destination_package and (
318+
self.pick_pack_same_time
319+
or self.unload_package_at_destination
320+
or self.multiple_move_single_pack
321+
):
322+
raise exceptions.UserError(
323+
_(
324+
"'No destination package required' is incompatible with "
325+
"'Pick and pack at the same time',"
326+
"'Unload package at destination' and 'Multiple moves "
327+
"same destination package'."
328+
)
329+
)
297330

298331
@api.depends("scenario_id", "picking_type_ids")
299332
def _compute_move_create_is_possible(self):
@@ -497,6 +530,13 @@ def _compute_move_line_search_sort_order_is_possible(self):
497530
"allow_move_line_search_sort_order"
498531
)
499532

533+
@api.depends("scenario_id")
534+
def _compute_require_destination_package_is_possible(self):
535+
for menu in self:
536+
menu.require_destination_package_is_possible = menu.scenario_id.has_option(
537+
"require_destination_package"
538+
)
539+
500540
@api.constrains(
501541
"move_line_search_sort_order", "move_line_search_sort_order_custom_code"
502542
)

shopfloor/services/zone_picking.py

+38-38
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ def lines_order(self):
161161
def _pick_pack_same_time(self):
162162
return self.work.menu.pick_pack_same_time
163163

164+
def _packing_required(self):
165+
return self.work.menu.require_destination_package
166+
164167
def _handle_complete_mix_pack(self, package):
165168
packaging = self._actions_for("packaging")
166169
return (
@@ -924,7 +927,7 @@ def _set_destination_location(
924927
return (location_changed, response)
925928

926929
# If no destination package
927-
if not move_line.result_package_id:
930+
if self._packing_required() and not move_line.result_package_id:
928931
response = self._response_for_set_line_destination(
929932
move_line,
930933
message=self.msg_store.dest_package_required(),
@@ -1169,43 +1172,40 @@ def set_destination(
11691172
)
11701173

11711174
extra_message = ""
1172-
if moving_full_quantity:
1173-
# When the barcode is a location,
1174-
# only allow it if moving the full qty.
1175-
location = search.location_from_scan(barcode)
1176-
if location:
1177-
package = None
1178-
if handle_complete_mix_pack:
1179-
package = move_line.package_id
1180-
if self._pick_pack_same_time():
1181-
(
1182-
good_for_packing,
1183-
message,
1184-
) = self._handle_pick_pack_same_time_for_location(move_line)
1185-
# TODO: we should append the msg instead.
1186-
# To achieve this, we should refactor `response.message` to a list
1187-
# or, to no break backward compat, we could add `extra_messages`
1188-
# to allow backend to send a main message and N additional messages.
1189-
extra_message = message
1190-
if not good_for_packing:
1191-
return self._response_for_set_line_destination(
1192-
move_line, message=message, qty_done=quantity
1193-
)
1194-
pkg_moved, response = self._set_destination_location(
1195-
move_line,
1196-
package,
1197-
quantity,
1198-
confirmation,
1199-
location,
1200-
barcode,
1201-
)
1202-
if response:
1203-
if extra_message:
1204-
if response.get("message"):
1205-
response["message"]["body"] += "\n" + extra_message["body"]
1206-
else:
1207-
response["message"] = extra_message
1208-
return response
1175+
location = search.location_from_scan(barcode)
1176+
if location:
1177+
package = None
1178+
if handle_complete_mix_pack:
1179+
package = move_line.package_id
1180+
if self._pick_pack_same_time():
1181+
(
1182+
good_for_packing,
1183+
message,
1184+
) = self._handle_pick_pack_same_time_for_location(move_line)
1185+
# TODO: we should append the msg instead.
1186+
# To achieve this, we should refactor `response.message` to a list
1187+
# or, to no break backward compat, we could add `extra_messages`
1188+
# to allow backend to send a main message and N additional messages.
1189+
extra_message = message
1190+
if not good_for_packing:
1191+
return self._response_for_set_line_destination(
1192+
move_line, message=message, qty_done=quantity
1193+
)
1194+
pkg_moved, response = self._set_destination_location(
1195+
move_line,
1196+
package,
1197+
quantity,
1198+
confirmation,
1199+
location,
1200+
barcode,
1201+
)
1202+
if response:
1203+
if extra_message:
1204+
if response.get("message"):
1205+
response["message"]["body"] += "\n" + extra_message["body"]
1206+
else:
1207+
response["message"] = extra_message
1208+
return response
12091209

12101210
# When the barcode is a package
12111211
package = search.package_from_scan(barcode)

shopfloor/tests/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
from . import test_zone_picking_unload_single
8080
from . import test_zone_picking_unload_all
8181
from . import test_zone_picking_unload_set_destination
82+
from . import test_zone_picking_require_destination_package
8283
from . import test_misc
8384
from . import test_move_action_assign
8485
from . import test_scan_anything
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3+
from .test_zone_picking_base import ZonePickingCommonCase
4+
5+
# pylint: disable=missing-return
6+
7+
8+
class ZonePickingNoPAcking(ZonePickingCommonCase):
9+
"""Tests zone picking without packing steps.
10+
11+
* /set_destination
12+
13+
"""
14+
15+
def setUp(self):
16+
super().setUp()
17+
self.service.work.current_picking_type = self.picking1.picking_type_id
18+
self.picking1.move_line_ids.result_package_id = False
19+
20+
def test_set_destination(self):
21+
# when no packing is set, you can set the destination directly
22+
# without the need to pack the product
23+
self.service.work.menu.sudo().require_destination_package = True
24+
zone_location = self.zone_location
25+
picking_type = self.picking1.picking_type_id
26+
move_line = self.picking1.move_line_ids[0]
27+
response = self.service.dispatch(
28+
"set_destination",
29+
params={
30+
"move_line_id": move_line.id,
31+
"barcode": move_line.location_dest_id.barcode,
32+
"quantity": move_line.reserved_uom_qty,
33+
"confirmation": None,
34+
},
35+
)
36+
self.assert_response_set_line_destination(
37+
response,
38+
zone_location,
39+
picking_type,
40+
move_line,
41+
qty_done=move_line.reserved_uom_qty,
42+
message=self.service.msg_store.dest_package_required(),
43+
)
44+
self.service.work.menu.sudo().require_destination_package = False
45+
response = self.service.dispatch(
46+
"set_destination",
47+
params={
48+
"move_line_id": move_line.id,
49+
"barcode": move_line.location_dest_id.barcode,
50+
"quantity": move_line.reserved_uom_qty,
51+
"confirmation": None,
52+
},
53+
)
54+
move_lines = self.service._find_location_move_lines()
55+
move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
56+
self.assert_response_select_line(
57+
response,
58+
zone_location,
59+
picking_type,
60+
move_lines,
61+
message=self.service.msg_store.confirm_pack_moved(),
62+
)

shopfloor/tests/test_zone_picking_set_line_destination.py

+29-14
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,15 @@ def test_set_destination_location_no_other_move_line_partial_qty(self):
187187
move qty 10 (assigned):
188188
-> move_line qty 10 from location X
189189
190-
Then the operator move 6 qty on 10, we get:
191-
192-
an error because we can move only full qty by location
193-
and only a package barcode is allowed on scan.
190+
Then the operator move 6 qty on 10:
191+
-> move_line qty 6 from location X (done)
192+
-> move_line qty 4 from location X (assigned)
194193
"""
195194
zone_location = self.zone_location
196195
picking_type = self.picking3.picking_type_id
197196
barcode = self.packing_location.barcode
198197
moves_before = self.picking3.move_ids
198+
self.assertEqual(moves_before.product_uom_qty, 10)
199199
self.assertEqual(len(moves_before), 1)
200200
self.assertEqual(len(moves_before.move_line_ids), 1)
201201
move_line = moves_before.move_line_ids
@@ -210,14 +210,21 @@ def test_set_destination_location_no_other_move_line_partial_qty(self):
210210
"confirmation": None,
211211
},
212212
)
213-
self.assert_response_set_line_destination(
213+
move_lines = self.service._find_location_move_lines()
214+
move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
215+
self.assert_response_select_line(
214216
response,
215217
zone_location,
216218
picking_type,
217-
move_line,
218-
qty_done=6,
219-
message=self.service.msg_store.package_not_found_for_barcode(barcode),
219+
move_lines,
220+
message=self.service.msg_store.confirm_pack_moved(),
220221
)
222+
done_move = move_line.move_id
223+
assigned_move = moves_before
224+
self.assertEqual(done_move.state, "done")
225+
self.assertEqual(done_move.product_uom_qty, 6)
226+
self.assertEqual(assigned_move.state, "assigned")
227+
self.assertEqual(assigned_move.product_uom_qty, 4)
221228

222229
def test_set_destination_location_several_move_line_full_qty(self):
223230
"""Scanned barcode is the destination location.
@@ -297,8 +304,8 @@ def test_set_destination_location_several_move_line_partial_qty(self):
297304
298305
Then the operator move 4 qty on 6 (from the first move line), we get:
299306
300-
an error because we can move only full qty by location
301-
and only a package barcode is allowed on scan.
307+
-> move_line qty 6 from location X (assigned)
308+
-> move_line qty 4 from location Y (done)
302309
"""
303310
zone_location = self.zone_location
304311
picking_type = self.picking4.picking_type_id
@@ -318,14 +325,22 @@ def test_set_destination_location_several_move_line_partial_qty(self):
318325
"confirmation": None,
319326
},
320327
)
321-
self.assert_response_set_line_destination(
328+
# Check response
329+
move_lines = self.service._find_location_move_lines()
330+
move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
331+
self.assert_response_select_line(
322332
response,
323333
zone_location,
324334
picking_type,
325-
move_line,
326-
qty_done=4,
327-
message=self.service.msg_store.package_not_found_for_barcode(barcode),
335+
move_lines,
336+
message=self.service.msg_store.confirm_pack_moved(),
328337
)
338+
done_move = move_line.move_id
339+
assigned_move = moves_before
340+
self.assertEqual(done_move.state, "done")
341+
self.assertEqual(done_move.product_uom_qty, 4)
342+
self.assertEqual(assigned_move.state, "assigned")
343+
self.assertEqual(assigned_move.product_uom_qty, 6)
329344

330345
def test_set_destination_location_zero_check(self):
331346
"""Scanned barcode is the destination location.

0 commit comments

Comments
 (0)