Skip to content

Commit

Permalink
Merge commit 'refs/pull/712/head' of github.com:OCA/wms into merge-br…
Browse files Browse the repository at this point in the history
…anch-2477-BSCOS-4099-10871866
  • Loading branch information
sebalix committed Nov 13, 2023
2 parents e1a196a + 7877df4 commit c3177ba
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 19 deletions.
19 changes: 19 additions & 0 deletions shopfloor/actions/change_package_lot.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ def is_lesser(value, other, rounding):
# lines
move_line.move_id._action_assign()

self._change_pack_lot_change_lot__before_assign(
previous_lot, lot, to_assign_moves
)
# Find other available goods for the lines which were using the
# lot before...
to_assign_moves._action_assign()
Expand All @@ -170,6 +173,11 @@ def is_lesser(value, other, rounding):
message["body"] = "{} {}".format(message["body"], " ".join(message_parts))
return response_ok_func(move_line, message=message)

def _change_pack_lot_change_lot__before_assign(
self, previous_lot, lot, to_assign_moves
):
pass

def _package_content_replacement_allowed(self, package, move_line):
# we can't replace by a package which doesn't contain the product...
return move_line.product_id in package.quant_ids.product_id
Expand Down Expand Up @@ -215,3 +223,14 @@ def change_package(self, move_line, package, response_ok_func, response_error_fu
else:
message = self.msg_store.units_replaced_by_package(package)
return response_ok_func(move_line, message=message)

def filter_lines_allowed_to_change_lot(self, move_lines, lot):
"""Filter move lines allowed to change their lot.
We cannot change a lot on a move having ancestors. That would mean we
already picked up the wrong lot on the previous move(s) and Odoo already
restricts the reservation based on the previous move(s).
"""
return move_lines.filtered(
lambda l: (l.product_id == lot.product_id and not l.move_id.move_orig_ids)
)
37 changes: 37 additions & 0 deletions shopfloor/actions/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ def package_different_change(self):
),
}

def lot_different_change(self):
return {
"message_type": "warning",
"body": _(
"You scanned a different lot with the same product, "
"do you want to change lot? Scan it again to confirm. "
"The first line matching this product will be updated. "
),
}

def package_not_available_in_picking(self, package, picking):
return {
"message_type": "warning",
Expand Down Expand Up @@ -526,6 +536,12 @@ def lot_multiple_packages_scan_package(self):
"body": _("This lot is part of multiple packages, please scan a package."),
}

def lot_not_found(self):
return {
"message_type": "error",
"body": _("This lot does not exist anymore."),
}

def lot_not_found_in_pickings(self):
return {
"message_type": "warning",
Expand Down Expand Up @@ -897,3 +913,24 @@ def package_transfer_not_allowed_scan_location(self):
"please scan a location instead."
),
}

def lot_changed(self):
return {
"message_type": "info",
"body": _("Lot changed"),
}

def lot_change_wrong_lot(self, lot_name):
return {
"message_type": "error",
"body": _("Scanned lot differs from the previous scan: %(lot)s.")
% {
"lot": lot_name,
},
}

def lot_change_no_line_found(self):
return {
"message_type": "error",
"body": _("Unable to find a line with the same product but different lot."),
}
108 changes: 93 additions & 15 deletions shopfloor/services/checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,29 @@ class Checkout(Component):
_description = __doc__

def _response_for_select_line(
self, picking, message=None, need_confirm_pack_all=False
self, picking, message=None, need_confirm_pack_all=False, need_confirm_lot=None
):
if all(line.shopfloor_checkout_done for line in picking.move_line_ids):
return self._response_for_summary(picking, message=message)
return self._response(
next_state="select_line",
data=self._data_for_select_line(
picking, need_confirm_pack_all=need_confirm_pack_all
picking,
need_confirm_pack_all=need_confirm_pack_all,
need_confirm_lot=need_confirm_lot,
),
message=message,
)

def _data_for_select_line(self, picking, need_confirm_pack_all=False):
def _data_for_select_line(
self, picking, need_confirm_pack_all=False, need_confirm_lot=None
):
return {
"picking": self._data_for_stock_picking(picking),
"group_lines_by_location": True,
"show_oneline_package_content": self.work.menu.show_oneline_package_content,
"need_confirm_pack_all": need_confirm_pack_all,
"need_confirm_lot": need_confirm_lot,
}

def _response_for_summary(self, picking, need_confirm=False, message=None):
Expand Down Expand Up @@ -420,7 +425,7 @@ def _deselect_lines(self, lines):
{"qty_done": 0, "shopfloor_user_id": False}
)

def scan_line(self, picking_id, barcode, confirm_pack_all=False):
def scan_line(self, picking_id, barcode, confirm_pack_all=False, confirm_lot=None):
"""Scan move lines of the stock picking
It allows to select move lines of the stock picking for the next
Expand Down Expand Up @@ -451,7 +456,7 @@ def scan_line(self, picking_id, barcode, confirm_pack_all=False):

search_result = self._scan_line_find(picking, barcode)
result_handler = getattr(self, "_select_lines_from_" + search_result.type)
kw = {"confirm_pack_all": confirm_pack_all}
kw = {"confirm_pack_all": confirm_pack_all, "confirm_lot": confirm_lot}
return result_handler(picking, selection_lines, search_result.record, **kw)

def _scan_line_find(self, picking, barcode, search_types=None):
Expand Down Expand Up @@ -503,6 +508,7 @@ def _select_lines_from_package(
def _select_lines_from_product(
self, picking, selection_lines, product, prefill_qty=1, check_lot=True, **kw
):
# TODO: should we propagate 'kw.get("message")' content on each return?
if product.tracking in ("lot", "serial") and check_lot:
return self._response_for_select_line(
picking, message=self.msg_store.scan_lot_on_product_tracked_by_lot()
Expand Down Expand Up @@ -532,7 +538,11 @@ def _select_lines_from_product(
# Select all the lines of the package when we scan a product in a
# package and we have only one.
return self._select_lines_from_package(
picking, selection_lines, packages, prefill_qty=prefill_qty
picking,
selection_lines,
packages,
prefill_qty=prefill_qty,
message=kw.get("message"),
)
else:
# There is no package on selected lines, so also select all other lines
Expand All @@ -545,7 +555,9 @@ def _select_lines_from_product(
lines = self._select_lines(
lines, prefill_qty=prefill_qty, related_lines=related_lines
)
return self._response_for_select_package(picking, lines)
return self._response_for_select_package(
picking, lines, message=kw.get("message")
)

def _select_lines_from_packaging(self, picking, selection_lines, packaging, **kw):
return self._select_lines_from_product(
Expand All @@ -555,15 +567,58 @@ def _select_lines_from_packaging(self, picking, selection_lines, packaging, **kw
def _select_lines_from_lot(
self, picking, selection_lines, lot, prefill_qty=1, **kw
):
lines = selection_lines.filtered(lambda l: l.lot_id == lot)
message = None
lines = self._picking_lines_by_lot(picking, selection_lines, lot)
if not lines:
return self._response_for_select_line(
picking,
message={
"message_type": "error",
"body": _("Lot is not in the current transfer."),
},
change_package_lot = self._actions_for("change.package.lot")
if not kw.get("confirm_lot"):
lines_same_product = (
change_package_lot.filter_lines_allowed_to_change_lot(
selection_lines, lot
)
)
# If there's at least one product matching we are good to go.
# In any case, only the 1st line matching will be affected.
if lines_same_product:
return self._response_for_select_line(
picking,
message=self.msg_store.lot_different_change(),
need_confirm_lot=lot.id,
)
return self._response_for_select_line(
picking,
message=self.msg_store.lot_not_found_in_picking(lot, picking),
)
# Validate the scanned lot against the previous one
if lot.id != kw["confirm_lot"]:
expected_lot = lot.browse(kw["confirm_lot"]).exists()
return self._response_for_select_line(
picking,
message=self.msg_store.lot_change_wrong_lot(expected_lot.name),
)
# Change lot confirmed
line = fields.first(
selection_lines.filtered(
lambda l: l.product_id == lot.product_id and l.lot_id != lot
)
)
if not line:
return self._response_for_select_line(
picking,
message=self.msg_store.lot_change_no_line_found(),
)
response_ok_func = self._change_lot_response_handler_ok
response_error_func = self._change_lot_response_handler_error
message = change_package_lot.change_lot(
line, lot, response_ok_func, response_error_func
)
if message["message_type"] == "error":
return self._response_for_select_line(picking, message=message)
else:
lines = line
# Some lines have been recreated, refresh the recordset
# to avoid CacheMiss error
selection_lines = self._lines_to_pack(picking)

# When lots are as units outside of packages, we can select them for
# packing, but if they are in a package, we want the user to scan the packages.
Expand All @@ -574,6 +629,8 @@ def _select_lines_from_lot(
# package, but also if we have one lot as a package and the same lot as
# a unit in another line. In both cases, we want the user to scan the
# package.
# NOTE: change_pack_lot already checked this, so if we changed the lot
# we are already safe.
if packages and len({line.package_id for line in lines}) > 1:
return self._response_for_select_line(
picking, message=self.msg_store.lot_multiple_packages_scan_package()
Expand All @@ -582,7 +639,11 @@ def _select_lines_from_lot(
# Select all the lines of the package when we scan a lot in a
# package and we have only one.
return self._select_lines_from_package(
picking, selection_lines, packages, prefill_qty=prefill_qty, **kw
picking,
selection_lines,
packages,
prefill_qty=prefill_qty,
message=message,
)

first_allowed_line = fields.first(lines)
Expand All @@ -592,8 +653,19 @@ def _select_lines_from_lot(
first_allowed_line.product_id,
prefill_qty=prefill_qty,
check_lot=False,
message=message,
)

def _picking_lines_by_lot(self, picking, selection_lines, lot):
"""Control filtering of selected lines by given lot."""
return selection_lines.filtered(lambda l: l.lot_id == lot)

def _change_lot_response_handler_ok(self, move_line, message=None):
return message

def _change_lot_response_handler_error(self, move_line, message=None):
return message

def _select_lines_from_serial(self, picking, selection_lines, lot, **kw):
# Search for serial number is actually the same as searching for lot (as of v14...)
return self._select_lines_from_lot(picking, selection_lines, lot, **kw)
Expand Down Expand Up @@ -1479,6 +1551,11 @@ def scan_line(self):
"nullable": True,
"required": False,
},
"confirm_lot": {
"type": "integer",
"nullable": True,
"required": False,
},
}

def select_line(self):
Expand Down Expand Up @@ -1701,6 +1778,7 @@ def _schema_stock_picking_details(self):
group_lines_by_location={"type": "boolean"},
show_oneline_package_content={"type": "boolean"},
need_confirm_pack_all={"type": "boolean"},
need_confirm_lot={"type": "integer", "nullable": True},
)

@property
Expand Down
1 change: 1 addition & 0 deletions shopfloor/tests/test_checkout_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def _data_for_select_line(self, picking, **kw):
"group_lines_by_location": True,
"show_oneline_package_content": False,
"need_confirm_pack_all": False,
"need_confirm_lot": None,
}
data.update(kw)
return data
Expand Down
45 changes: 41 additions & 4 deletions shopfloor/tests/test_checkout_scan_line.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _

from .test_checkout_scan_line_base import CheckoutScanLineCaseBase


Expand Down Expand Up @@ -132,7 +134,7 @@ def test_scan_line_product_in_one_package_all_package_lines_ok(self):
# more than one package, it would be an error.
self._test_scan_line_ok(self.product_a.barcode, picking.move_line_ids)

def _test_scan_line_error(self, picking, barcode, message):
def _test_scan_line_error(self, picking, barcode, message, need_confirm_lot=None):
"""Test errors for /scan_line
:param picking: the picking we are currently working with (selected)
Expand All @@ -145,7 +147,9 @@ def _test_scan_line_error(self, picking, barcode, message):
self.assert_response(
response,
next_state="select_line",
data=self._data_for_select_line(picking),
data=dict(
self._data_for_select_line(picking), need_confirm_lot=need_confirm_lot
),
message=message,
)

Expand Down Expand Up @@ -247,17 +251,50 @@ def test_scan_line_error_product_not_in_picking(self):
},
)

def test_scan_line_error_lot_not_in_picking(self):
def test_scan_line_error_lot_different_change_success(self):
"""Scan the wrong lot while a line with the same product exists."""
picking = self._create_picking(lines=[(self.product_a, 10)])
self._fill_stock_for_moves(picking.move_lines, in_lot=True)
picking.action_assign()
previous_lot = picking.move_line_ids.lot_id
# Create a lot that is not registered in the location we are working on
# so a draft inventory for control is generated automatically when the
# lot is changed.
lot = self.env["stock.production.lot"].create(
{"product_id": self.product_a.id, "company_id": self.env.company.id}
)
self._test_scan_line_error(
picking,
lot.name,
{"message_type": "error", "body": "Lot is not in the current transfer."},
self.msg_store.lot_different_change(),
need_confirm_lot=lot.id,
)
# Second scan to confirm the change of lot
response = self.service.dispatch(
"scan_line",
params={
"picking_id": picking.id,
"barcode": lot.name,
"confirm_lot": lot.id,
},
)
message = self.msg_store.lot_replaced_by_lot(previous_lot, lot)
inventory_message = _("A draft inventory has been created for control.")
message["body"] = f"{message['body']} {inventory_message}"
self.assert_response(
response,
next_state="select_package",
data={
"selected_move_lines": [
self._move_line_data(ml) for ml in picking.move_line_ids
],
"picking": self.service.data.picking(picking),
"packing_info": self.service._data_for_packing_info(picking),
"no_package_enabled": not self.service.options.get(
"checkout__disable_no_package"
),
},
message=message,
)

def test_scan_line_error_lot_in_two_packages(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const checkout_states = function ($instance) {
picking_id: $instance.state.data.picking.id,
barcode: scanned.text,
confirm_pack_all: $instance.state.data.need_confirm_pack_all,
confirm_lot: $instance.state.data.need_confirm_lot,
})
);
},
Expand Down

0 comments on commit c3177ba

Please sign in to comment.