diff --git a/stock_barcodes/models/__init__.py b/stock_barcodes/models/__init__.py index ff0a0135c92c..f8c60224e85b 100644 --- a/stock_barcodes/models/__init__.py +++ b/stock_barcodes/models/__init__.py @@ -1,5 +1,6 @@ from . import stock_barcodes_action from . import stock_barcodes_option +from . import stock_move from . import stock_move_line from . import stock_picking from . import stock_picking_type diff --git a/stock_barcodes/models/stock_move.py b/stock_barcodes/models/stock_move.py new file mode 100644 index 000000000000..4d5ad452fca2 --- /dev/null +++ b/stock_barcodes/models/stock_move.py @@ -0,0 +1,37 @@ +# Copyright 2024 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class StockMove(models.Model): + _inherit = "stock.move" + + barcode_backorder_action = fields.Selection( + [ + ("pending", "Pending"), + ("create_backorder", "Create Backorder"), + ("skip_backorder", "No Backorder"), + ], + string="Backorder action", + default="pending", + ) + + def _action_done(self, cancel_backorder=False): + moves_cancel_backorder = self.browse() + if not cancel_backorder: + moves_cancel_backorder = self.filtered( + lambda sm: sm.barcode_backorder_action == "skip_backorder" + ) + super(StockMove, moves_cancel_backorder)._action_done(cancel_backorder=True) + moves_backorder = self - moves_cancel_backorder + moves_backorder.barcode_backorder_action = "pending" + return super(StockMove, moves_backorder)._action_done( + cancel_backorder=cancel_backorder + ) + + def copy_data(self, default=None): + vals_list = super().copy_data(default=default) + for vals in vals_list: + vals.pop("barcode_backorder_action", None) + return vals_list diff --git a/stock_barcodes/models/stock_move_line.py b/stock_barcodes/models/stock_move_line.py index f5c622dddbdb..3d042506334b 100644 --- a/stock_barcodes/models/stock_move_line.py +++ b/stock_barcodes/models/stock_move_line.py @@ -28,19 +28,13 @@ def _barcodes_process_line_to_unlink(self): def action_barcode_detailed_operation_unlink(self): for sml in self: - if sml.reserved_uom_qty: - sml._barcodes_process_line_to_unlink() - else: - sml.unlink() + stock_move = sml.move_id + stock_move.barcode_backorder_action = "pending" + sml.unlink() # HACK: To force refresh wizard values wiz_barcode = self.env["wiz.stock.barcodes.read.picking"].browse( self.env.context.get("wiz_barcode_id", False) ) - if wiz_barcode.option_group_id.barcode_guided_mode == "guided": - wiz_barcode.todo_line_id.line_ids = wiz_barcode.todo_line_id.line_ids - if not any(wiz_barcode.todo_line_id.line_ids.mapped("qty_done")): - wiz_barcode.fill_todo_records() - wiz_barcode.determine_todo_action() - else: - wiz_barcode.fill_todo_records() - wiz_barcode.todo_line_id.line_ids = wiz_barcode.todo_line_id.line_ids + stock_move._action_assign() + wiz_barcode.fill_todo_records() + wiz_barcode.determine_todo_action() diff --git a/stock_barcodes/models/stock_picking.py b/stock_barcodes/models/stock_picking.py index 3dbf7723d1cf..b3970c062b21 100644 --- a/stock_barcodes/models/stock_picking.py +++ b/stock_barcodes/models/stock_picking.py @@ -1,7 +1,6 @@ # Copyright 2019 Sergio Teruel # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo import models -from odoo.tools.float_utils import float_compare class StockPicking(models.Model): @@ -33,8 +32,8 @@ def action_barcode_scan(self, option_group=False): wiz = self.env["wiz.stock.barcodes.read.picking"].create( self._prepare_barcode_wiz_vals(option_group) ) - wiz.determine_todo_action() wiz.fill_pending_moves() + wiz.determine_todo_action() action = self.env["ir.actions.actions"]._for_xml_id( "stock_barcodes.action_stock_barcodes_read_picking" ) @@ -42,41 +41,16 @@ def action_barcode_scan(self, option_group=False): return action def button_validate(self): - if ( - self.picking_type_id.barcode_option_group_id.auto_put_in_pack - and not self.move_line_ids.mapped("result_package_id") - ): - self.action_put_in_pack() - create_backorder = False + put_in_pack_picks = self.filtered( + lambda p: p.picking_type_id.barcode_option_group_id.auto_put_in_pack + and not p.move_line_ids.result_package_id + ) + if put_in_pack_picks: + put_in_pack_picks.action_put_in_pack() # Variable initialized as True to optimize break loop - skip_backorder = True if self.env.context.get("stock_barcodes_validate_picking", False): - # Avoid backorder when all move lines are processed (done or done_forced) - prec = self.env["decimal.precision"].precision_get( - "Product Unit of Measure" - ) - for move in self.move_ids.filtered(lambda sm: sm.state != "cancel"): - if ( - float_compare( - move.quantity_done, move.product_uom_qty, precision_digits=prec - ) - < 0 - ): - # In normal conditions backorder will be created - create_backorder = True - if not move.move_line_ids or any( - sml.barcode_scan_state in ["pending"] - for sml in move.move_line_ids - ): - # If any move are not processed we can not skip backorder - skip_backorder = False - break - if create_backorder and skip_backorder: res = super( - StockPicking, - self.with_context( - picking_ids_not_to_backorder=self.ids, skip_backorder=True - ), + StockPicking, self.with_context(skip_backorder=True) ).button_validate() else: res = super().button_validate() diff --git a/stock_barcodes/models/stock_picking_type.py b/stock_barcodes/models/stock_picking_type.py index 6f0277723fb5..f83b5b3d5eb7 100644 --- a/stock_barcodes/models/stock_picking_type.py +++ b/stock_barcodes/models/stock_picking_type.py @@ -46,8 +46,8 @@ def action_barcode_scan(self): ): vals["location_dest_id"] = self.default_location_dest_id.id wiz = self.env["wiz.stock.barcodes.read.picking"].create(vals) - wiz.determine_todo_action() wiz.fill_pending_moves() + wiz.determine_todo_action() action = self.env["ir.actions.actions"]._for_xml_id( "stock_barcodes.action_stock_barcodes_read_picking" ) diff --git a/stock_barcodes/tests/test_stock_barcodes.py b/stock_barcodes/tests/test_stock_barcodes.py index 97dda779c6f2..1f66f73e4cd4 100644 --- a/stock_barcodes/tests/test_stock_barcodes.py +++ b/stock_barcodes/tests/test_stock_barcodes.py @@ -48,6 +48,14 @@ def test_wizard_scan_package(self): self.assertEqual(self.wiz_scan.product_qty, 15.0) self.wiz_scan.manual_entry = False + # Force more than one package with the same lot + self.product_wo_tracking.packaging_ids.barcode = "5420008510489" + self.action_barcode_scanned(self.wiz_scan, "5420008510489") + self.assertEqual( + self.wiz_scan.message, + "5420008510489 (More than one package found)", + ) + def test_wizard_scan_lot(self): self.wiz_scan.location_id = self.location_1.id self.wiz_scan.action_show_step() diff --git a/stock_barcodes/wizard/stock_barcodes_read.py b/stock_barcodes/wizard/stock_barcodes_read.py index fe8f79cefef7..9ca3278c644d 100644 --- a/stock_barcodes/wizard/stock_barcodes_read.py +++ b/stock_barcodes/wizard/stock_barcodes_read.py @@ -3,6 +3,7 @@ import logging from odoo import _, api, fields, models +from odoo.tools import float_round _logger = logging.getLogger(__name__) @@ -64,9 +65,7 @@ class WizStockBarcodesRead(models.AbstractModel): comodel_name="stock.barcodes.action", compute="_compute_action_ids" ) option_group_id = fields.Many2one(comodel_name="stock.barcodes.option.group") - visible_force_done = fields.Boolean( - compute="_compute_visible_force_done", store=True, readonly=False - ) + visible_force_done = fields.Boolean() step = fields.Integer() is_manual_qty = fields.Boolean(compute="_compute_is_manual_qty") is_manual_confirm = fields.Boolean(compute="_compute_is_manual_qty") @@ -134,6 +133,22 @@ def _compute_qty_available(self): domain_quant, ["quantity"], [], orderby="id" ) self.qty_available = groups[0]["quantity"] + # Unexpected done quantities must reduce qty_available + if self.lot_id: + done_move_lines = self.move_line_ids.filtered( + lambda m: m.product_id == self.product_id and m.lot_id == self.lot_id + ) + else: + done_move_lines = self.move_line_ids.filtered( + lambda m: m.product_id == self.product_id + ) + for sml in done_move_lines: + over_done_qty = float_round( + sml.qty_done - sml.reserved_uom_qty, + precision_rounding=sml.product_uom_id.rounding, + ) + if over_done_qty > 0.0: + self.qty_available -= over_done_qty @api.depends("product_id") def _compute_display_assign_serial(self): @@ -150,7 +165,7 @@ def onchange_packaging_qty(self): if self.packaging_id: self.product_qty = self.packaging_qty * self.packaging_id.qty - @api.depends( + @api.onchange( "product_id", "lot_id", "package_id", @@ -158,7 +173,7 @@ def onchange_packaging_qty(self): "packaging_qty", "product_qty", ) - def _compute_visible_force_done(self): + def onchange_visible_force_done(self): self.visible_force_done = False def _set_messagge_info(self, message_type, message): @@ -253,6 +268,8 @@ def process_barcode_lot_id(self): "more_match", _("No stock available for this lot with screen values"), ) + self.lot_id = False + self.lot_name = False return False if quants: self.set_info_from_quants(quants) @@ -366,8 +383,15 @@ def set_info_from_quants(self, quants): def process_barcode_packaging_id(self): domain = self._barcode_domain(self.barcode) if self.env.user.has_group("product.group_stock_packaging"): + domain.append(("product_id", "!=", False)) packaging = self.env["product.packaging"].search(domain) if packaging: + if len(packaging) > 1: + self._set_messagge_info( + "more_match", _("More than one package found") + ) + self.packaging_id = False + return False self.action_packaging_scaned_post(packaging) return True return False @@ -389,14 +413,15 @@ def process_barcode(self, barcode): option_func = getattr(self, "process_barcode_%s" % option.field_name, False) if option_func: res = option_func() - if option.required: - self.play_sounds(res) if res: barcode_found = True + self.play_sounds(barcode_found) break elif self.message_type != "success": + self.play_sounds(False) return False if not barcode_found: + self.play_sounds(barcode_found) if self.option_group_id.ignore_filled_fields: self._set_messagge_info( "info", _("Barcode not found or field already filled") @@ -405,12 +430,6 @@ def process_barcode(self, barcode): self._set_messagge_info( "not_found", _("Barcode not found with this screen values") ) - self.display_notification( - self.barcode, - message_type="danger", - title=_("Barcode not found"), - sticky=False, - ) return False if not self.check_option_required(): return False @@ -721,9 +740,12 @@ def onchange_package_id(self): def action_confirm(self): if not self.check_option_required(): + self.play_sounds(False) return False + record = self.browse(self.ids) + record.write(self._convert_to_write(self._cache)) + self = record res = self.action_done() - # self.invalidate_recordset() self.refresh_data() self.play_sounds(res) self._set_focus_on_qty_input() @@ -829,8 +851,3 @@ def display_notification( "stock_barcodes_notify-{}".format(self.ids[0]), message, ) - - def refresh_data(self): - self.env["bus.bus"]._sendone( - "barcode_reload", "stock_barcodes_refresh_data", {} - ) diff --git a/stock_barcodes/wizard/stock_barcodes_read_picking.py b/stock_barcodes/wizard/stock_barcodes_read_picking.py index aa35ba77d794..e5962f5b0dd7 100644 --- a/stock_barcodes/wizard/stock_barcodes_read_picking.py +++ b/stock_barcodes/wizard/stock_barcodes_read_picking.py @@ -1,11 +1,13 @@ # Copyright 2019 Sergio Teruel # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging +from collections import OrderedDict, defaultdict from odoo import _, api, fields, models from odoo.exceptions import ValidationError from odoo.fields import first from odoo.tools.float_utils import float_compare +from odoo.tools.safe_eval import safe_eval _logger = logging.getLogger(__name__) @@ -74,6 +76,8 @@ def _field_candidate_ids(self): picking_location_id = fields.Many2one(related="picking_id.location_id") picking_location_dest_id = fields.Many2one(related="picking_id.location_dest_id") company_id = fields.Many2one(related="picking_id.company_id") + todo_line_is_extra_line = fields.Boolean(related="todo_line_id.is_extra_line") + forced_todo_key = fields.Char() @api.depends("todo_line_id") def _compute_todo_line_display_ids(self): @@ -144,8 +148,8 @@ def onchange_picking_id(self): # view, so for create a candidate picking with the same default picking # we need create it in this onchange self._set_default_picking() - self.determine_todo_action() self.fill_pending_moves() + self.determine_todo_action() def get_sorted_move_lines(self, move_lines): location_field = self.option_group_id.location_field_to_sort @@ -183,12 +187,8 @@ def _get_stock_move_lines_todo(self): return move_lines def fill_pending_moves(self): - if ( - self.option_group_id.barcode_guided_mode != "guided" - and self.option_group_id.show_pending_moves - and not self.todo_line_ids - ): - self.fill_todo_records() + # TODO: Unify method + self.fill_todo_records() def get_moves_or_move_lines(self): if self.option_group_id.source_pending_moves == "move_line_ids": @@ -196,9 +196,12 @@ def get_moves_or_move_lines(self): else: return self.picking_id.move_ids + def get_moves(self): + return self.picking_id.move_ids + def fill_todo_records(self): move_lines = self.get_sorted_move_lines(self.get_moves_or_move_lines()) - self.env["wiz.stock.barcodes.read.todo"].fill_records(self, [move_lines]) + self.fill_records([move_lines]) @api.model def _get_fields_filled_special(self): @@ -214,12 +217,6 @@ def determine_todo_action(self, forced_todo_line=False): self.visible_force_done = self.env.context.get("visible_force_done", False) if not self.option_group_id.barcode_guided_mode == "guided": return False - if not self.todo_line_ids: - self.fill_todo_records() - # When scanning all information in one step (e.g. using GS-1), the - # status and qty processed might have not been update, we ensure it - # invalidating the cache. - self.todo_line_ids.invalidate_recordset() self.todo_line_id = ( forced_todo_line or self.todo_line_ids.filtered(lambda t: t._origin.state == "pending")[:1] @@ -277,12 +274,34 @@ def action_done(self): if not self.keep_screen_values or self.todo_line_id.state != "pending": if not self.env.context.get("skip_clean_values", False): self.action_clean_values() - self.determine_todo_action() + keep_vals = {} + else: + keep_vals = self._convert_to_write(self._cache) + self.fill_todo_records() + if self.forced_todo_key: + self.todo_line_id = self.pending_move_ids.filtered( + lambda ln: str(self._group_key(ln)) == self.forced_todo_key + )[:1] + self.selected_pending_move_id = self.todo_line_id + self.determine_todo_action(self.todo_line_id) else: - self.action_show_step() + self.determine_todo_action() + self.action_show_step() + if keep_vals: + self.update_keep_values(keep_vals) + # Force refresh candidate pickings to show green if not pending moves + if not self.pending_move_ids: + self._set_candidate_pickings(self.picking_id) return move_dic return res + def update_keep_values(self, keep_vals): + options = self.option_group_id.option_ids + fields_to_keep = options.filtered( + lambda op: self._fields[op.field_name].type != "float" + ).mapped("field_name") + self.update({f_name: keep_vals[f_name] for f_name in fields_to_keep}) + def action_manual_entry(self): result = super().action_manual_entry() if result: @@ -333,11 +352,10 @@ def _states_move_allowed(self): def _prepare_stock_moves_domain(self): domain = [ + ("product_id", "=", self.product_id.id), ("picking_id.picking_type_id.code", "=", self.picking_type_code), ("state", "in", self._states_move_allowed()), ] - if self.product_id: - domain.append(("product_id", "=", self.product_id.id)) if self.picking_id: domain.append(("picking_id", "=", self.picking_id.id)) return domain @@ -613,13 +631,14 @@ def _process_stock_move_line(self): # noqa: C901 # link this new lines to the todo line details # If user scan a product distinct of the todo line we need link to other # alternative move - if move_to_link_in_todo_line and self.todo_line_id: - todo_line = self.todo_line_id - else: - todo_line = self.todo_line_ids.filtered( - lambda ln: ln.product_id == self.product_id - ) - todo_line.line_ids = [(4, sml.id) for sml in stock_move_lines] + if self.option_group_id.source_pending_moves != "move_line_ids": + if move_to_link_in_todo_line and self.todo_line_id: + todo_line = self.todo_line_id + else: + todo_line = self.todo_line_ids.filtered( + lambda ln: ln.product_id == self.product_id + ) + todo_line.line_ids = [(4, sml.id) for sml in stock_move_lines] self.update_fields_after_process_stock(moves_todo) return move_lines_dic @@ -774,6 +793,155 @@ def _option_required_hook(self, option_required): return bool(self.location_dest_id) return super()._option_required_hook(option_required) + def _group_key(self, line): + group_key_for_todo_records = self.option_group_id.group_key_for_todo_records + if group_key_for_todo_records: + return safe_eval(group_key_for_todo_records, globals_dict={"object": line}) + if self.option_group_id.source_pending_moves == "move_line_ids": + return ( + line.location_id.id, + line.product_id.id, + line.lot_id.id, + line.package_id.id, + ) + else: + return (line.location_id.id, line.product_id.id) + + def _get_all_products_quantities_in_package(self, package): + res = {} + # TODO: Check if domain is applied and we must recover _get_contained_quants + for quant in package.quant_ids: + if quant.product_id not in res: + res[quant.product_id] = 0 + res[quant.product_id] += quant.quantity + return res + + def _prepare_fill_record_values(self, line, position): + vals = { + "wiz_barcode_id": self.id, + "product_id": line.product_id.id, + "name": "To do action", + "position_index": position, + "picking_code": line.picking_code, + } + if line._name == "stock.move.line": + package_product_dic = self._get_all_products_quantities_in_package( + line.package_id + ) + vals.update( + { + "location_id": line.location_id.id, + "location_dest_id": line.location_dest_id.id, + "lot_id": line.lot_id.id, + "package_id": line.package_id.id, + "result_package_id": line.result_package_id.id, + "uom_id": line.product_uom_id.id, + "product_uom_qty": line.reserved_uom_qty, + "product_qty_reserved": line.reserved_qty, + "line_ids": [(6, 0, line.ids)], + "stock_move_ids": [(6, 0, line.move_id.ids)], + "package_product_qty": package_product_dic + and package_product_dic[line.product_id] + or 0.0, + "is_stock_move_line_origin": True, + } + ) + else: + vals.update( + { + "location_id": (line.move_line_ids[:1] or line).location_id.id, + "location_dest_id": ( + line.move_line_ids[:1] or line + ).location_dest_id.id, + "uom_id": line.product_uom.id, + "product_uom_qty": line.product_uom_qty, + "product_qty_reserved": line.move_line_ids + # TODO: Use reserved_qty or reserved_uom_qty + and sum(line.move_line_ids.mapped("reserved_qty")) + or line.product_uom_qty, + "line_ids": [(6, 0, line.move_line_ids.ids)], + "stock_move_ids": [(6, 0, line.ids)], + "is_stock_move_line_origin": False, + } + ) + return vals + + def _update_fill_record_values(self, line, vals): + if vals["is_stock_move_line_origin"]: + vals["product_uom_qty"] += line.reserved_uom_qty + vals["product_qty_reserved"] += line.product_qty + vals["line_ids"][0][2].append(line.id) + vals["stock_move_ids"][0][2].append(line.move_id.id) + else: + vals["product_uom_qty"] += line.product_uom_qty + vals["product_qty_reserved"] += ( + line.move_line_ids + # TODO: Use reserved_qty or reserved_uom_qty + and sum(line.move_line_ids.mapped("reserved_qty")) + or line.product_uom_qty + ) + vals["line_ids"][0][2].extend(line.move_line_ids.ids) + vals["stock_move_ids"][0][2].extend(line.ids) + return vals + + @api.model + def fill_records(self, lines_list): + """ + :param lines_list: browse list + :return: + """ + self.forced_todo_key = str( + self._group_key(self.todo_line_id or self.selected_pending_move_id) + ) + self.todo_line_ids.unlink() + self.todo_line_id = False + # self.position_index = 0 + todo_vals = OrderedDict() + position = 0 + move_qty_dic = defaultdict(float) + is_stock_move_line_origin = lines_list[0]._name == "stock.move.line" + for lines in lines_list: + for line in lines: + key = self._group_key(line) + if key not in todo_vals: + todo_vals[key] = self._prepare_fill_record_values(line, position) + position += 1 + else: + todo_vals[key] = self._update_fill_record_values( + line, todo_vals[key] + ) + if is_stock_move_line_origin: + move_qty_dic[line.move_id] += max( + line.reserved_uom_qty, line.qty_done + ) + else: + move_qty_dic[line] += max(line.product_uom_qty, line.quantity_done) + for move in self.get_moves(): + qty = move_qty_dic[move] + if ( + move.barcode_backorder_action == "pending" + and move.product_uom_qty > qty + ): + vals = self._prepare_fill_record_values(move, position) + vals.update( + { + "product_uom_qty": move.product_uom_qty - qty, + "product_qty_reserved": 0.0, + "line_ids": False, + "is_extra_line": True, + } + ) + todo_vals[ + ( + move, + "M", + ) + ] = vals + position += 1 + self.todo_line_ids = self.env["wiz.stock.barcodes.read.todo"].create( + list(todo_vals.values()) + ) + class WizCandidatePicking(models.TransientModel): """ diff --git a/stock_barcodes/wizard/stock_barcodes_read_picking_views.xml b/stock_barcodes/wizard/stock_barcodes_read_picking_views.xml index ee8a45b67a0b..d691310aa6d1 100644 --- a/stock_barcodes/wizard/stock_barcodes_read_picking_views.xml +++ b/stock_barcodes/wizard/stock_barcodes_read_picking_views.xml @@ -135,6 +135,7 @@ + + + + {'invisible': [('todo_line_is_extra_line', '!=', False)]} + - + + + + # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from collections import OrderedDict from odoo import api, fields, models -from odoo.tools.safe_eval import safe_eval +from odoo.tools.float_utils import float_compare class WizStockBarcodesReadTodo(models.TransientModel): @@ -62,113 +61,27 @@ class WizStockBarcodesReadTodo(models.TransientModel): stock_move_ids = fields.Many2many(comodel_name="stock.move") position_index = fields.Integer() picking_code = fields.Char("Type of Operation") - - def _group_key(self, wiz, line): - group_key_for_todo_records = wiz.option_group_id.group_key_for_todo_records - if group_key_for_todo_records: - return safe_eval(group_key_for_todo_records, globals_dict={"object": line}) - if wiz.option_group_id.source_pending_moves == "move_line_ids": - return (line.location_id, line.product_id, line.lot_id, line.package_id) - else: - return (line.location_id, line.product_id) - - def _get_all_products_quantities_in_package(self, package): - res = {} - for quant in package.quant_ids: - if quant.product_id not in res: - res[quant.product_id] = 0 - res[quant.product_id] += quant.quantity - return res - - def _prepare_fill_record_values(self, wiz_barcode, line, position): - vals = { - "product_id": line.product_id.id, - "name": "To do action", - "position_index": position, - "picking_code": line.picking_code, - } - if wiz_barcode.option_group_id.source_pending_moves == "move_line_ids": - package_product_dic = self._get_all_products_quantities_in_package( - line.package_id - ) - vals.update( - { - "product_uom_qty": line.reserved_uom_qty, - "location_id": line.location_id.id, - "location_dest_id": line.location_dest_id.id, - "lot_id": line.lot_id.id, - "package_id": line.package_id.id, - "result_package_id": line.result_package_id.id, - "uom_id": line.product_uom_id.id, - "product_qty_reserved": line.reserved_qty, - "line_ids": [(6, 0, line.ids)], - "stock_move_ids": [(6, 0, line.move_id.ids)], - "package_product_qty": package_product_dic - and package_product_dic[line.product_id] - or 0.0, - } - ) - else: - vals.update( - { - "product_uom_qty": line.product_uom_qty, - "location_id": (line.move_line_ids[:1] or line).location_id.id, - "location_dest_id": ( - line.move_line_ids[:1] or line - ).location_dest_id.id, - "uom_id": line.product_uom.id, - "product_qty_reserved": line.move_line_ids - and sum(line.move_line_ids.mapped("reserved_qty")) - or line.product_uom_qty, - "line_ids": [(6, 0, line.move_line_ids.ids)], - "stock_move_ids": [(6, 0, line.ids)], - } - ) - return vals - - def _update_fill_record_values(self, wiz_barcode, line, vals): - if wiz_barcode.option_group_id.source_pending_moves == "move_line_ids": - vals["product_uom_qty"] += line.reserved_uom_qty - vals["product_qty_reserved"] += line.reserved_qty - vals["line_ids"][0][2].append(line.id) - vals["stock_move_ids"][0][2].append(line.move_id.id) - else: - vals["product_uom_qty"] += line.product_uom_qty - vals["product_qty_reserved"] += ( - line.move_line_ids - and sum(line.move_line_ids.mapped("reserved_qty")) - or line.product_uom_qty - ) - vals["line_ids"][0][2].extend(line.move_line_ids.ids) - vals["stock_move_ids"][0][2].extend(line.ids) - return vals - - @api.model - def fill_records(self, wiz_barcode, lines_list): - """ - :param lines_list: browse list - :return: - """ - wiz_barcode.todo_line_ids = self.browse() - todo_vals = OrderedDict() - position = 0 - for lines in lines_list: - for line in lines: - key = self._group_key(wiz_barcode, line) - if key not in todo_vals: - todo_vals[key] = self._prepare_fill_record_values( - wiz_barcode, line, position - ) - position += 1 - else: - todo_vals[key] = self._update_fill_record_values( - wiz_barcode, line, todo_vals[key] - ) - wiz_barcode.todo_line_ids = self.create(list(todo_vals.values())) + is_extra_line = fields.Boolean() + # Used in kanban view + is_stock_move_line_origin = fields.Boolean() def action_todo_next(self): self.state = "done_forced" self.line_ids.barcode_scan_state = "done_forced" + for sml in self.line_ids: + if sml.reserved_uom_qty != sml.qty_done and sml.move_id.state != "waiting": + sml.reserved_uom_qty = sml.qty_done + if self.is_extra_line or not self.is_stock_move_line_origin: + barcode_backorder_action = self.env.context.get( + "barcode_backorder_action", "create_backorder" + ) + self.stock_move_ids.barcode_backorder_action = barcode_backorder_action + if barcode_backorder_action == "pending": + self.stock_move_ids.move_line_ids.unlink() + self.stock_move_ids._action_assign() + wiz_barcode = self.wiz_barcode_id + self.wiz_barcode_id.fill_todo_records() + self.wiz_barcode_id = wiz_barcode self.wiz_barcode_id.determine_todo_action() def action_reset_lines(self): @@ -176,6 +89,7 @@ def action_reset_lines(self): self.line_ids.barcode_scan_state = "pending" self.line_ids.qty_done = 0.0 self.wiz_barcode_id.action_clean_values() + self.wiz_barcode_id.fill_todo_records() self.wiz_barcode_id.determine_todo_action() def action_back_line(self): @@ -203,11 +117,21 @@ def _compute_qty_done(self): ) def _compute_state(self): for rec in self: - if rec.qty_done >= rec.product_uom_qty or ( + if float_compare( + rec.qty_done, + rec.product_uom_qty, + precision_rounding=rec.uom_id.rounding, + ) > -1 or ( rec.wiz_barcode_id.option_group_id.source_pending_moves == "move_line_ids" and rec.line_ids - and not any(ln.barcode_scan_state == "pending" for ln in rec.line_ids) + and ( + sum(rec.stock_move_ids.mapped("quantity_done")) + >= sum(rec.stock_move_ids.mapped("product_uom_qty")) + or not any( + ln.barcode_scan_state == "pending" for ln in rec.line_ids + ) + ) ): rec.state = "done" else: diff --git a/stock_barcodes/wizard/stock_barcodes_read_todo_view.xml b/stock_barcodes/wizard/stock_barcodes_read_todo_view.xml index 7abcf9dc540a..d6e418ea82f1 100644 --- a/stock_barcodes/wizard/stock_barcodes_read_todo_view.xml +++ b/stock_barcodes/wizard/stock_barcodes_read_todo_view.xml @@ -20,11 +20,13 @@ + +
@@ -103,6 +105,10 @@ t-esc="record.product_uom_qty.value" /> + NOT AVAILABLE
@@ -159,7 +165,45 @@ class="btn-sm float-end btn btn-primary" context="{'wiz_barcode_id': parent.id}" data-hotkey="4" - /> + > + + +
+
+
+
+ + +