Skip to content

Commit

Permalink
Merge pull request #42 from simahawk/misc-fixes
Browse files Browse the repository at this point in the history
Misc fixes for zone picking
  • Loading branch information
simahawk authored Aug 21, 2020
2 parents 8a36269 + ea7cd5f commit ab2753e
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 82 deletions.
98 changes: 73 additions & 25 deletions shopfloor/services/zone_picking.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import functools

from odoo.fields import first
from odoo.tools.float_utils import float_compare
from odoo.tools.float_utils import float_compare, float_is_zero

from odoo.addons.base_rest.components.service import to_bool, to_int
from odoo.addons.component.core import Component
Expand Down Expand Up @@ -488,13 +488,15 @@ def scan_source(self, zone_location_id, picking_type_id, barcode, order="priorit
def _set_destination_location(
self, zone_location, picking_type, move_line, quantity, confirmation, location
):
location_changed = False
response = None
# Ask confirmation to the user if the scanned location is not in the
# expected ones but is valid (in picking type's default destination)
if not location.is_sublocation_of(move_line.location_dest_id) and (
not confirmation
and location.is_sublocation_of(picking_type.default_location_dest_id)
):
return self._response_for_set_line_destination(
response = self._response_for_set_line_destination(
zone_location,
picking_type,
move_line,
Expand All @@ -503,27 +505,30 @@ def _set_destination_location(
),
confirmation_required=True,
)
return (location_changed, response)
# A valid location is a sub-location of the original destination, or a
# sub-location of the picking type's default destination location if
# `confirmation is True
if not location.is_sublocation_of(move_line.location_dest_id) and (
confirmation
and not location.is_sublocation_of(picking_type.default_location_dest_id)
):
return self._response_for_set_line_destination(
response = self._response_for_set_line_destination(
zone_location,
picking_type,
move_line,
message=self.msg_store.dest_location_not_allowed(),
)
return (location_changed, response)
# If no destination package
if not move_line.result_package_id:
return self._response_for_set_line_destination(
response = self._response_for_set_line_destination(
zone_location,
picking_type,
move_line,
message=self.msg_store.dest_package_required(),
)
return (location_changed, response)
# destination location set to the scanned one
move_line.location_dest_id = location
# the quantity done is set to the passed quantity
Expand All @@ -535,12 +540,14 @@ def _set_destination_location(
# try to re-assign any split move (in case of partial qty)
if "confirmed" in move_line.picking_id.move_lines.mapped("state"):
move_line.picking_id.action_assign()
location_changed = True
# Zero check
zero_check = picking_type.shopfloor_zero_check
if zero_check and move_line.location_id.planned_qty_in_location_is_empty():
return self._response_for_zero_check(
response = self._response_for_zero_check(
zone_location, picking_type, move_line.location_id
)
return (location_changed, response)

def _is_package_empty(self, package):
return not bool(package.quant_ids)
Expand All @@ -555,41 +562,55 @@ def _is_package_already_used(self, package):
)
)

def _move_line_compare_qty(self, move_line, qty):
rounding = move_line.product_uom_id.rounding
return float_compare(
qty, move_line.product_uom_qty, precision_rounding=rounding
)

def _move_line_full_qty(self, move_line, qty):
rounding = move_line.product_uom_id.rounding
return float_is_zero(
move_line.product_uom_qty - qty, precision_rounding=rounding
)

def _set_destination_package(
self, zone_location, picking_type, move_line, quantity, package
):
package_changed = False
response = None
# A valid package is:
# * an empty package
# * not used as destination for another move line
if not self._is_package_empty(package):
return self._response_for_set_line_destination(
response = self._response_for_set_line_destination(
zone_location,
picking_type,
move_line,
message=self.msg_store.package_not_empty(package),
)
return (package_changed, response)
if self._is_package_already_used(package):
return self._response_for_set_line_destination(
response = self._response_for_set_line_destination(
zone_location,
picking_type,
move_line,
message=self.msg_store.package_already_used(package),
)
return (package_changed, response)
# the quantity done is set to the passed quantity
# but if we move a partial qty, we need to split the move line
rounding = move_line.product_uom_id.rounding
compare = float_compare(
quantity, move_line.product_uom_qty, precision_rounding=rounding
)
compare = self._move_line_compare_qty(move_line, quantity)
qty_lesser = compare == -1
qty_greater = compare == 1
if qty_greater:
return self._response_for_set_line_destination(
response = self._response_for_set_line_destination(
zone_location,
picking_type,
move_line,
message=self.msg_store.unable_to_pick_more(move_line.product_uom_qty),
)
return (package_changed, response)
elif qty_lesser:
# split the move line which will be processed later
remaining = move_line.product_uom_qty - quantity
Expand All @@ -605,12 +626,14 @@ def _set_destination_package(
move_line.result_package_id = package
# the field ``shopfloor_user_id`` is updated with the current user
move_line.shopfloor_user_id = self.env.user
package_changed = True
# Zero check
zero_check = picking_type.shopfloor_zero_check
if zero_check and move_line.location_id.planned_qty_in_location_is_empty():
return self._response_for_zero_check(
response = self._response_for_zero_check(
zone_location, picking_type, move_line.location_id
)
return (package_changed, response)

def set_destination(
self,
Expand Down Expand Up @@ -674,29 +697,50 @@ def set_destination(
move_line = self.env["stock.move.line"].browse(move_line_id)
if not move_line.exists():
return self._response_for_start(message=self.msg_store.record_not_found())

pkg_moved = False
search = self.actions_for("search")
# When the barcode is a location
location = search.location_from_scan(barcode)
if location:
response = self._set_destination_location(
zone_location, picking_type, move_line, quantity, confirmation, location
)
if response:
return response
accept_only_package = not self._move_line_full_qty(move_line, quantity)

if not accept_only_package:
# When the barcode is a location
location = search.location_from_scan(barcode)
if location:
pkg_moved, response = self._set_destination_location(
zone_location,
picking_type,
move_line,
quantity,
confirmation,
location,
)
if response:
return response

# When the barcode is a package
package = search.package_from_scan(barcode)
if package:
location = move_line.location_dest_id
response = self._set_destination_package(
pkg_moved, response = self._set_destination_package(
zone_location, picking_type, move_line, quantity, package
)
if response:
return response

message = None

if not pkg_moved and not package and accept_only_package:
message = self.msg_store.package_not_found_for_barcode(barcode)
return self._response_for_set_line_destination(
zone_location, picking_type, move_line, message=message
)

if pkg_moved:
message = self.msg_store.confirm_pack_moved()

# Process the next line
response = self.list_move_lines(zone_location.id, picking_type.id)
return self._response(
base_response=response, message=self.msg_store.confirm_pack_moved(),
)
return self._response(base_response=response, message=message,)

def is_zero(self, zone_location_id, picking_type_id, move_line_id, zero):
"""Confirm or not if the source location of a move has zero qty
Expand Down Expand Up @@ -1175,6 +1219,7 @@ def unload_set_destination(
move_lines,
message=self.msg_store.record_not_found(),
)

buffer_lines = self._find_buffer_move_lines(
zone_location, picking_type, dest_package=package
)
Expand Down Expand Up @@ -1221,6 +1266,9 @@ def unload_set_destination(
return self._response_for_start(
message=self.msg_store.picking_type_complete(picking_type)
)
# TODO: when we have no lines here
# we should not redirect to `unload_set_destination`
# because we'll have nothing to display (currently the UI is broken).
return self._response_for_unload_set_destination(
zone_location,
picking_type,
Expand Down
63 changes: 14 additions & 49 deletions shopfloor/tests/test_zone_picking_set_line_destination.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,12 @@ def test_set_destination_location_no_other_move_line_partial_qty(self):
Then the operator move 6 qty on 10, we get:
move qty 6 (done):
-> move_line qty 6 from location X
move qty 4 (assigned):
-> move_line qty 4 from location Y (remaining)
an error because we can move only full qty by location
and only a package barcode is allowed on scan.
"""
zone_location = self.zone_location
picking_type = self.picking3.picking_type_id
barcode = self.packing_location.barcode
moves_before = self.picking3.move_lines
self.assertEqual(len(moves_before), 1)
self.assertEqual(len(moves_before.move_line_ids), 1)
Expand All @@ -207,30 +206,17 @@ def test_set_destination_location_no_other_move_line_partial_qty(self):
"zone_location_id": zone_location.id,
"picking_type_id": picking_type.id,
"move_line_id": move_line.id,
"barcode": self.packing_location.barcode,
"barcode": barcode,
"quantity": 6,
"confirmation": False,
},
)
# Check picking data (move has been split in two, 6 done and 4 remaining)
moves_after = self.picking3.move_lines
self.assertEqual(len(moves_after), 2)
self.assertEqual(moves_after[0].product_uom_qty, 6)
self.assertEqual(moves_after[0].state, "done")
self.assertEqual(moves_after[0].move_line_ids.product_uom_qty, 0)
self.assertEqual(moves_after[1].product_uom_qty, 4)
self.assertEqual(moves_after[1].state, "assigned")
self.assertEqual(moves_after[1].move_line_ids.product_uom_qty, 4)
self.assertEqual(move_line.qty_done, 6)
# Check response
move_lines = self.service._find_location_move_lines(zone_location, picking_type)
move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
self.assert_response_select_line(
self.assert_response_set_line_destination(
response,
zone_location,
picking_type,
move_lines,
message=self.service.msg_store.confirm_pack_moved(),
move_line,
message=self.service.msg_store.package_not_found_for_barcode(barcode),
)

def test_set_destination_location_several_move_line_full_qty(self):
Expand Down Expand Up @@ -308,56 +294,35 @@ def test_set_destination_location_several_move_line_partial_qty(self):
Then the operator move 4 qty on 6 (from the first move line), we get:
move qty 4 (done):
-> move_line qty 4 from location X
move qty 2 (assigned):
-> move_line qty 2 from location X (remaining)
move qty 4 (assigned):
-> move_line qty 4 from location Y (untouched)
an error because we can move only full qty by location
and only a package barcode is allowed on scan.
"""
zone_location = self.zone_location
picking_type = self.picking4.picking_type_id
barcode = self.packing_location.barcode
moves_before = self.picking4.move_lines
self.assertEqual(len(moves_before), 1) # 10 qty
self.assertEqual(len(moves_before.move_line_ids), 2) # 6+4 qty
move_line = moves_before.move_line_ids[0]
# we need a destination package if we want to scan a destination location
move_line.result_package_id = self.free_package
other_move_line = moves_before.move_line_ids[1]
response = self.service.dispatch(
"set_destination",
params={
"zone_location_id": zone_location.id,
"picking_type_id": picking_type.id,
"move_line_id": move_line.id,
"barcode": self.packing_location.barcode,
"barcode": barcode,
"quantity": 4, # 4/6 qty
"confirmation": False,
},
)
# Check picking data (move has been split in three, 4 done, 2+4 remaining)
moves_after = self.picking4.move_lines
self.assertEqual(len(moves_after), 3)
self.assertEqual(moves_after[0].product_uom_qty, 4)
self.assertEqual(moves_after[0].state, "done")
self.assertEqual(moves_after[0].move_line_ids.product_uom_qty, 0)
self.assertEqual(moves_after[1].product_uom_qty, 4)
self.assertEqual(moves_after[1].state, "assigned")
self.assertEqual(moves_after[1].move_line_ids.product_uom_qty, 4)
self.assertEqual(moves_after[2].product_uom_qty, 2)
self.assertEqual(moves_after[2].state, "assigned")
self.assertEqual(moves_after[2].move_line_ids.product_uom_qty, 2)
self.assertEqual(move_line.qty_done, 4)
self.assertNotEqual(move_line.move_id, other_move_line.move_id)
# Check response
move_lines = self.service._find_location_move_lines(zone_location, picking_type)
move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
self.assert_response_select_line(
self.assert_response_set_line_destination(
response,
zone_location,
picking_type,
move_lines,
message=self.service.msg_store.confirm_pack_moved(),
move_line,
message=self.service.msg_store.package_not_found_for_barcode(barcode),
)

def test_set_destination_location_zero_check(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export var ClusterPicking = Vue.component("cluster-picking", {
).quantity;
},
on_qty_edit: qty => {
this.scan_destination_qty = parseInt(qty);
this.scan_destination_qty = parseInt(qty, 10);
},
on_scan: scanned => {
this.wait_call(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export var LocationContentTransfer = Vue.component("location-content-transfer",
return {
usage: "location_content_transfer",
initial_state_key: "scan_location",
scan_destination_qty: 0,
states: {
init: {
enter: () => {
Expand Down Expand Up @@ -271,7 +272,7 @@ export var LocationContentTransfer = Vue.component("location-content-transfer",
qty_edit: "on_qty_update",
},
on_qty_update: qty => {
this.state.data.destination_qty = qty;
this.scan_destination_qty = parseInt(qty, 10);
},
on_scan: scanned => {
let endpoint, endpoint_data;
Expand All @@ -291,9 +292,7 @@ export var LocationContentTransfer = Vue.component("location-content-transfer",
location_id: data.move_line.location_src.id,
barcode: scanned.text,
confirmation: data.confirmation_required,
quantity:
this.state.data.destination_qty ||
data.move_line.quantity,
quantity: this.scan_destination_qty,
};
}
this.wait_call(this.odoo.call(endpoint, endpoint_data));
Expand Down
Loading

0 comments on commit ab2753e

Please sign in to comment.