mirror of
https://github.com/bringout/oca-warehouse.git
synced 2026-04-22 07:41:59 +02:00
905 lines
34 KiB
Python
905 lines
34 KiB
Python
# Copyright 2019 Sergio Teruel <sergio.teruel@tecnativa.com>
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
import logging
|
|
|
|
from odoo import _, api, fields, models
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
TYPE_ERROR = ["more_match", "not_found"]
|
|
|
|
|
|
class WizStockBarcodesRead(models.AbstractModel):
|
|
_name = "wiz.stock.barcodes.read"
|
|
_inherit = "barcodes.barcode_events_mixin"
|
|
_description = "Wizard to read barcode"
|
|
# To prevent remove the record wizard until 2 days old
|
|
_transient_max_hours = 48
|
|
_allowed_product_types = ["product", "consu"]
|
|
_rec_name = "barcode"
|
|
|
|
barcode = fields.Char()
|
|
res_model_id = fields.Many2one(comodel_name="ir.model", index=True)
|
|
res_id = fields.Integer(index=True)
|
|
product_id = fields.Many2one(
|
|
comodel_name="product.product", domain=[("type", "in", _allowed_product_types)]
|
|
)
|
|
product_uom_id = fields.Many2one(comodel_name="uom.uom")
|
|
product_tracking = fields.Selection(related="product_id.tracking", readonly=True)
|
|
lot_id = fields.Many2one(comodel_name="stock.lot")
|
|
lot_name = fields.Char(
|
|
"Lot/Serial Number Name",
|
|
compute="_compute_lot_name",
|
|
readonly=False,
|
|
store=True,
|
|
)
|
|
location_id = fields.Many2one(comodel_name="stock.location")
|
|
location_dest_id = fields.Many2one(
|
|
comodel_name="stock.location", string="Location dest."
|
|
)
|
|
packaging_id = fields.Many2one(comodel_name="product.packaging")
|
|
product_packaging_ids = fields.One2many(related="product_id.packaging_ids")
|
|
package_id = fields.Many2one(comodel_name="stock.quant.package")
|
|
result_package_id = fields.Many2one(comodel_name="stock.quant.package")
|
|
owner_id = fields.Many2one(comodel_name="res.partner")
|
|
packaging_qty = fields.Float(string="Package Qty", digits="Product Unit of Measure")
|
|
product_qty = fields.Float(digits="Product Unit of Measure")
|
|
manual_entry = fields.Boolean(string="Manual", help="Entry manual data")
|
|
confirmed_moves = fields.Boolean(
|
|
string="Confirmed moves", related="option_group_id.confirmed_moves"
|
|
)
|
|
message_type = fields.Selection(
|
|
[
|
|
("info", "Barcode read with additional info"),
|
|
("info_page", "Info page"),
|
|
("not_found", "No barcode found"),
|
|
("more_match", "More than one matches found"),
|
|
("success", "Barcode read correctly"),
|
|
],
|
|
readonly=True,
|
|
)
|
|
message = fields.Char(readonly=True)
|
|
message_step = fields.Char(readonly=True)
|
|
guided_product_id = fields.Many2one(comodel_name="product.product")
|
|
guided_location_id = fields.Many2one(comodel_name="stock.location")
|
|
guided_location_dest_id = fields.Many2one(comodel_name="stock.location")
|
|
guided_lot_id = fields.Many2one(comodel_name="stock.lot")
|
|
action_ids = fields.Many2many(
|
|
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()
|
|
step = fields.Integer()
|
|
is_manual_qty = fields.Boolean(compute="_compute_is_manual_qty")
|
|
is_manual_confirm = fields.Boolean(compute="_compute_is_manual_qty")
|
|
# Technical field to allow use in attrs
|
|
display_menu = fields.Boolean()
|
|
auto_lot = fields.Boolean(
|
|
string="Get lots automatically",
|
|
help="If checked the lot will be set automatically with the same "
|
|
"removal startegy",
|
|
compute="_compute_auto_lot",
|
|
store=True,
|
|
readonly=False,
|
|
)
|
|
create_lot = fields.Boolean(
|
|
string="Allow create lot",
|
|
help="Show lot name field",
|
|
compute="_compute_create_lot",
|
|
)
|
|
display_assign_serial = fields.Boolean(compute="_compute_display_assign_serial")
|
|
keep_result_package = fields.Boolean()
|
|
total_product_uom_qty = fields.Float(
|
|
string="Product Demand", digits="Product Unit of Measure", store=False
|
|
)
|
|
total_product_qty_done = fields.Float(
|
|
string="Product Qty. Done", digits="Product Unit of Measure", store=False
|
|
)
|
|
|
|
enable_add_product = fields.Boolean(default=True)
|
|
|
|
@api.depends("res_id")
|
|
def _compute_action_ids(self):
|
|
actions = self.env["stock.barcodes.action"].search(
|
|
[("action_window_id", "!=", False)]
|
|
)
|
|
self.action_ids = actions
|
|
|
|
@api.depends("option_group_id")
|
|
def _compute_is_manual_qty(self):
|
|
for rec in self:
|
|
rec.is_manual_qty = rec.option_group_id.is_manual_qty
|
|
rec.is_manual_confirm = rec.option_group_id.is_manual_confirm
|
|
rec.auto_lot = rec.option_group_id.auto_lot
|
|
|
|
@api.depends("option_group_id")
|
|
def _compute_auto_lot(self):
|
|
for rec in self:
|
|
rec.auto_lot = rec.option_group_id.auto_lot
|
|
|
|
@api.depends("option_group_id")
|
|
def _compute_create_lot(self):
|
|
for rec in self:
|
|
rec.create_lot = rec.option_group_id.create_lot
|
|
|
|
@api.depends("product_id")
|
|
def _compute_display_assign_serial(self):
|
|
for rec in self:
|
|
rec.display_assign_serial = rec.product_id.tracking == "serial"
|
|
|
|
@api.depends("lot_id")
|
|
def _compute_lot_name(self):
|
|
for rec in self:
|
|
rec.lot_name = rec.lot_id.name
|
|
|
|
@api.onchange("packaging_qty")
|
|
def onchange_packaging_qty(self):
|
|
if self.packaging_id:
|
|
self.product_qty = self.packaging_qty * self.packaging_id.qty
|
|
|
|
@api.onchange(
|
|
"product_id",
|
|
"lot_id",
|
|
"package_id",
|
|
"result_package_id",
|
|
"packaging_qty",
|
|
"product_qty",
|
|
)
|
|
def onchange_visible_force_done(self):
|
|
self.visible_force_done = False
|
|
|
|
def _set_messagge_info(self, message_type, message):
|
|
"""
|
|
Set message type and message description.
|
|
For manual entry mode barcode is not set so is not displayed
|
|
"""
|
|
self.message_type = message_type
|
|
# if self.barcode and self.message_type in ["more_match", "not_found"]:
|
|
if self.barcode:
|
|
self.message = _(
|
|
"%(barcode)s (%(message)s)", barcode=self.barcode, message=message
|
|
)
|
|
else:
|
|
if message_type in TYPE_ERROR:
|
|
self.manual_entry = True
|
|
self.send_bus_done(
|
|
"stock_barcodes_scan",
|
|
"actions_barcode_notification",
|
|
{
|
|
"message": message,
|
|
"sticky": True,
|
|
"message_type": "danger"
|
|
if message_type in TYPE_ERROR
|
|
else message_type,
|
|
},
|
|
)
|
|
elif message_type != "info_page":
|
|
self.send_bus_done(
|
|
"stock_barcodes_scan",
|
|
"actions_barcode_notification",
|
|
{
|
|
"message": message,
|
|
"message_type": message_type,
|
|
},
|
|
)
|
|
else:
|
|
self.message = "%s" % message
|
|
|
|
def process_barcode_location_id(self):
|
|
location = self.env["stock.location"].search(self._barcode_domain(self.barcode))
|
|
if location:
|
|
self.location_id = location
|
|
return True
|
|
return False
|
|
|
|
def process_barcode_location_dest_id(self):
|
|
location = self.env["stock.location"].search(self._barcode_domain(self.barcode))
|
|
if location:
|
|
self.location_dest_id = location
|
|
return True
|
|
return False
|
|
|
|
def process_barcode_product_id(self):
|
|
domain = self._barcode_domain(self.barcode)
|
|
product = self.env["product.product"].search(domain)
|
|
if product:
|
|
if len(product) > 1:
|
|
self._set_messagge_info("more_match", _("More than one product found"))
|
|
return False
|
|
elif product.type not in self._allowed_product_types:
|
|
self._set_messagge_info(
|
|
"not_found", _("The product type is not allowed")
|
|
)
|
|
return False
|
|
self.action_product_scaned_post(product)
|
|
if (
|
|
self.option_group_id.fill_fields_from_lot
|
|
and self.location_id
|
|
and self.product_id
|
|
):
|
|
quant_domain = [
|
|
("location_id", "=", self.location_id.id),
|
|
("product_id", "=", product.id),
|
|
]
|
|
if self.lot_id:
|
|
quant_domain.append(("lot_id", "=", self.lot_id.id))
|
|
if self.package_id:
|
|
quant_domain.append(("package_id", "=", self.package_id.id))
|
|
if self.owner_id:
|
|
quant_domain.append(("owner_id", "=", self.owner_id.id))
|
|
quants = self.env["stock.quant"].search(quant_domain)
|
|
if quants:
|
|
self.set_info_from_quants(quants)
|
|
return True
|
|
return False
|
|
|
|
def process_barcode_lot_id(self):
|
|
if self.env.user.has_group("stock.group_production_lot"):
|
|
lot_domain = [("name", "=", self.barcode)]
|
|
if self.product_id:
|
|
lot_domain.append(("product_id", "=", self.product_id.id))
|
|
lot = self.env["stock.lot"].search(lot_domain)
|
|
if len(lot) == 1:
|
|
if self.option_group_id.fill_fields_from_lot:
|
|
quant_domain = [
|
|
("lot_id.name", "=", self.barcode),
|
|
("product_id", "=", lot.product_id.id),
|
|
("quantity", ">", 0.0),
|
|
]
|
|
if self.location_id:
|
|
quant_domain.append(("location_id", "=", self.location_id.id))
|
|
else:
|
|
quant_domain.append(("location_id.usage", "=", "internal"))
|
|
if self.owner_id:
|
|
quant_domain.append(("owner_id", "=", self.owner_id.id))
|
|
quants = self.env["stock.quant"].search(quant_domain)
|
|
if (
|
|
not self._name == "wiz.stock.barcodes.read.inventory"
|
|
and not quants
|
|
and not self.option_group_id.allow_negative_quant
|
|
):
|
|
self._set_messagge_info(
|
|
"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)
|
|
else:
|
|
self.product_id = lot.product_id
|
|
self.action_lot_scaned_post(lot)
|
|
return True
|
|
else:
|
|
self.product_id = lot.product_id
|
|
self.action_lot_scaned_post(lot)
|
|
return True
|
|
elif lot:
|
|
self._set_messagge_info(
|
|
"more_match", _("More than one lot found\nScan product before")
|
|
)
|
|
elif (
|
|
self.product_id
|
|
and self.product_id.tracking != "none"
|
|
and self.option_group_id.create_lot
|
|
):
|
|
self.lot_name = self.barcode
|
|
self.action_lot_scaned_post(self.lot_name)
|
|
return True
|
|
return False
|
|
|
|
def process_barcode_package_id(self):
|
|
if not self.env.user.has_group("stock.group_tracking_lot"):
|
|
return False
|
|
quant_domain = [
|
|
("package_id.name", "=", self.barcode),
|
|
("quantity", ">", 0.0),
|
|
]
|
|
if self.option_group_id.get_option_value("location_id", "forced"):
|
|
quant_domain.append(("location_id", "=", self.location_id.id))
|
|
if self.owner_id:
|
|
quant_domain.append(("owner_id", "=", self.owner_id.id))
|
|
quants = self.env["stock.quant"].search(quant_domain)
|
|
internal_quants = quants.filtered(lambda q: q.location_id.usage == "internal")
|
|
if internal_quants:
|
|
quants = internal_quants
|
|
elif quants:
|
|
self = self.with_context(ignore_quant_location=True)
|
|
# self._set_messagge_info("more_match", _("Package located external location"))
|
|
else:
|
|
# self._set_messagge_info("more_match", _("Package not fount or empty"))
|
|
return False
|
|
self.set_info_from_quants(quants)
|
|
return True
|
|
|
|
def process_barcode_result_package_id(self):
|
|
if not self.env.user.has_group("stock.group_tracking_lot"):
|
|
return False
|
|
domain = [("name", "=", self.barcode)]
|
|
package = self.env["stock.quant.package"].search(domain)
|
|
if package:
|
|
self.result_package_id = package[:1]
|
|
return True
|
|
return False
|
|
|
|
def set_info_from_quants(self, quants):
|
|
"""
|
|
Fill wizard fields from stock quants
|
|
"""
|
|
if self.env.context.get("skip_set_info_from_quants"):
|
|
return
|
|
ignore_quant_location = self.env.context.get(
|
|
"ignore_quant_location", self.option_group_id.ignore_quant_location
|
|
)
|
|
if len(quants) == 1:
|
|
# All ok
|
|
self.action_product_scaned_post(quants.product_id)
|
|
self.package_id = quants.package_id
|
|
self.result_package_id = quants.package_id
|
|
if quants.lot_id:
|
|
self.action_lot_scaned_post(quants.lot_id)
|
|
if quants.owner_id:
|
|
self.owner_id = quants.owner_id
|
|
# Review conditions
|
|
if (
|
|
not ignore_quant_location
|
|
and not self.option_group_id.get_option_value("location_id", "forced")
|
|
and self.option_group_id.code != "IN"
|
|
):
|
|
self.location_id = quants.location_id
|
|
if self.option_group_id.code != "OUT" and not self.env.context.get(
|
|
"skip_update_quantity_from_lot", False
|
|
):
|
|
self.product_qty = quants.quantity
|
|
elif len(quants) > 1:
|
|
# More than one record found with same barcode.
|
|
# Could be half lot in two distinct locations.
|
|
# Empty location field to force a location barcode scan
|
|
products = quants.mapped("product_id")
|
|
if len(products) == 1:
|
|
self.action_product_scaned_post(products[0])
|
|
package = quants[0].package_id
|
|
if not quants.filtered(lambda q: q.package_id != package):
|
|
self.package_id = package
|
|
lots = quants.mapped("lot_id")
|
|
if len(lots) == 1:
|
|
self.action_lot_scaned_post(lots[0])
|
|
owner = quants[0].owner_id
|
|
if not quants.filtered(lambda q: q.owner_id != owner):
|
|
self.owner_id = owner
|
|
if not ignore_quant_location:
|
|
locations = quants.mapped("location_id")
|
|
if len(locations) == 1:
|
|
if not self.location_id and self.option_group_id.code != "IN":
|
|
self.location_id = locations
|
|
|
|
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
|
|
|
|
def process_barcode(self, barcode):
|
|
if not self:
|
|
barcode_action = self.env["stock.barcodes.action"].search(
|
|
[
|
|
("action_window_id", "!=", False),
|
|
("barcode", "=", barcode),
|
|
],
|
|
limit=1,
|
|
)
|
|
|
|
self.env["bus.bus"]._sendone(
|
|
"stock_barcodes_scan",
|
|
"actions_main_menu_barcode",
|
|
{
|
|
"action_ok": len(barcode_action) > 0,
|
|
"action": barcode_action.open_action() if barcode_action else "",
|
|
"barcode": barcode,
|
|
},
|
|
)
|
|
else:
|
|
self._set_messagge_info("success", _("OK"))
|
|
options = self.option_group_id.option_ids
|
|
barcode_found = False
|
|
options_to_scan = options.filtered("to_scan")
|
|
options_required = options.filtered("required")
|
|
options_to_scan = options_to_scan.filtered(lambda op: op.step == self.step)
|
|
for option in options_to_scan:
|
|
if (
|
|
self.option_group_id.ignore_filled_fields
|
|
and option in options_required
|
|
and getattr(self, option.field_name, False)
|
|
):
|
|
continue
|
|
option_func = getattr(
|
|
self, "process_barcode_%s" % option.field_name, False
|
|
)
|
|
if option_func:
|
|
res = option_func()
|
|
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(
|
|
"not_found", _("Barcode not found or field already filled")
|
|
)
|
|
else:
|
|
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
|
|
if self.is_manual_confirm or self.manual_entry:
|
|
self._set_messagge_info("info", _("Review and confirm"))
|
|
return False
|
|
return self.action_confirm()
|
|
|
|
def check_option_required(self):
|
|
options = self.option_group_id.option_ids
|
|
options_required = options.filtered("required")
|
|
for option in options_required:
|
|
if not getattr(self, option.field_name, False):
|
|
if self.is_manual_qty and option.field_name in [
|
|
"product_qty",
|
|
"packaging_qty",
|
|
]:
|
|
self._set_focus_on_qty_input("product_qty")
|
|
if option.field_name == "lot_id" and (
|
|
self.product_id.tracking == "none"
|
|
or self.auto_lot
|
|
or (self.lot_name and self.create_lot)
|
|
):
|
|
continue
|
|
if self._option_required_hook(option):
|
|
continue
|
|
self.display_notification(
|
|
_("{name} is required").format(name=option.name),
|
|
message_type="danger",
|
|
title=_("Empty field"),
|
|
sticky=False,
|
|
)
|
|
self.action_show_step()
|
|
return False
|
|
return True
|
|
|
|
def _option_required_hook(self, option_required):
|
|
"""Hook to evaluate is an option is required"""
|
|
return False
|
|
|
|
def _scanned_location(self, barcode):
|
|
location = self.env["stock.location"].search(self._barcode_domain(barcode))
|
|
if location:
|
|
self.location_id = location
|
|
self._set_messagge_info("info", _("Waiting product"))
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def _barcode_domain(self, barcode):
|
|
field_name = self.env.context.get("barcode_domain_field", "barcode")
|
|
return [(field_name, "=", barcode)]
|
|
|
|
def _clean_barcode_scanned(self, barcode):
|
|
return barcode.rstrip()
|
|
|
|
def on_barcode_scanned(self, barcode):
|
|
self.barcode = self._clean_barcode_scanned(barcode)
|
|
|
|
def dummy_on_barcode_scanned(self):
|
|
"""To avoid execute operations in onchange environment"""
|
|
self.process_barcode(self.barcode)
|
|
|
|
def check_location_contidion(self):
|
|
if not self.location_id:
|
|
self._set_messagge_info("info", _("Waiting location"))
|
|
# Remove product when no location has been scanned
|
|
self.product_id = False
|
|
return False
|
|
return True
|
|
|
|
def check_lot_contidion(self):
|
|
if self.product_id.tracking != "none" and not self.lot_id and not self.lot_name:
|
|
self._set_messagge_info("info", _("Waiting lot"))
|
|
return False
|
|
return True
|
|
|
|
def check_done_conditions(self):
|
|
result_ok = self.check_location_contidion()
|
|
if not result_ok:
|
|
return False
|
|
if not self.product_id:
|
|
self._set_messagge_info("info", _("Waiting product"))
|
|
return False
|
|
result_ok = self.check_lot_contidion()
|
|
if not result_ok:
|
|
return False
|
|
if (
|
|
not self.product_qty
|
|
and not self._name == "wiz.stock.barcodes.read.inventory"
|
|
):
|
|
self._set_messagge_info("info", _("Waiting quantities"))
|
|
return False
|
|
if (
|
|
self.option_group_id.barcode_guided_mode == "guided"
|
|
and not self._check_guided_values()
|
|
):
|
|
return False
|
|
if self.manual_entry:
|
|
self._set_messagge_info("success", _("Manual entry OK"))
|
|
return True
|
|
|
|
def _check_guided_values(self):
|
|
if (
|
|
self.product_id != self.guided_product_id
|
|
and self.option_group_id.get_option_value("product_id", "forced")
|
|
):
|
|
self._set_messagge_info("more_match", _("Wrong product"))
|
|
self.product_qty = 0.0
|
|
return False
|
|
if (
|
|
self.guided_product_id.tracking != "none"
|
|
and self.lot_id != self.guided_lot_id
|
|
and self.option_group_id.get_option_value("lot_id", "forced")
|
|
):
|
|
self._set_messagge_info("more_match", _("Wrong lot"))
|
|
return False
|
|
if (
|
|
self.location_id != self.guided_location_id
|
|
and self.option_group_id.get_option_value("location_id", "forced")
|
|
):
|
|
self._set_messagge_info("more_match", _("Wrong location"))
|
|
return False
|
|
if (
|
|
self.location_dest_id != self.guided_location_dest_id
|
|
and self.option_group_id.get_option_value("location_dest_id", "forced")
|
|
):
|
|
self._set_messagge_info("more_match", _("Wrong location dest"))
|
|
return False
|
|
return True
|
|
|
|
def action_done(self):
|
|
if not self.manual_entry and not self.product_qty and not self.is_manual_qty:
|
|
self.product_qty = 1.0
|
|
limit_product_qty = float(
|
|
self.env["ir.config_parameter"]
|
|
.sudo()
|
|
.get_param("stock_barcodes.limit_product_qty", "999999")
|
|
)
|
|
if self.product_qty > limit_product_qty:
|
|
# HACK: Some times users scan a barcode into input element.
|
|
# At this time, to prevent this we check that the quantity be realistic.
|
|
self._set_messagge_info("more_match", _("The quantity is huge"))
|
|
return False
|
|
if not self.check_done_conditions():
|
|
return False
|
|
self.process_lot_before_done()
|
|
return True
|
|
|
|
def action_cancel(self):
|
|
return True
|
|
|
|
def action_product_scaned_post(self, product):
|
|
self.package_id = False
|
|
if self.product_id != product and self.lot_id.product_id != product:
|
|
self.lot_id = False
|
|
self.product_id = product
|
|
self.product_uom_id = self.product_id.uom_id
|
|
self.set_product_qty()
|
|
|
|
def action_packaging_scaned_post(self, packaging):
|
|
self.packaging_id = packaging
|
|
if (
|
|
self.product_id != packaging.product_id
|
|
and self.lot_id.product_id != packaging.product_id
|
|
):
|
|
self.lot_id = False
|
|
self.product_id = packaging.product_id
|
|
self.set_product_qty()
|
|
|
|
def action_lot_scaned_post(self, lot):
|
|
if isinstance(lot, str):
|
|
self.lot_name = lot
|
|
else:
|
|
self.lot_id = lot
|
|
self.set_product_qty()
|
|
|
|
def set_product_qty(self):
|
|
if (
|
|
self.manual_entry
|
|
or self.is_manual_qty
|
|
or self.option_group_id.get_option_value("product_qty", "filled_default")
|
|
):
|
|
return
|
|
elif self.packaging_id:
|
|
self.packaging_qty = 1.0
|
|
self.product_qty = self.packaging_id.qty * self.packaging_qty
|
|
else:
|
|
self.packaging_qty = 0.0
|
|
self.product_qty = 1.0
|
|
|
|
def action_clean_lot(self):
|
|
self.lot_id = False
|
|
self.lot_name = False
|
|
self.action_show_step()
|
|
|
|
def action_clean_product(self):
|
|
self.product_id = False
|
|
self.action_show_step()
|
|
|
|
def action_clean_package(self):
|
|
self.package_id = False
|
|
self.result_package_id = False
|
|
self.action_show_step()
|
|
|
|
def action_create_package(self):
|
|
self.result_package_id = self.env["stock.quant.package"].create({})
|
|
|
|
def action_clean_values(self):
|
|
options = self.option_group_id.option_ids
|
|
options_to_clean = options.filtered(
|
|
lambda op: op.clean_after_done and op.field_name in self
|
|
)
|
|
for option in options_to_clean:
|
|
if option.field_name == "result_package_id" and self.keep_result_package:
|
|
continue
|
|
if option.field_name:
|
|
setattr(self, option.field_name, False)
|
|
self.action_show_step()
|
|
self.product_qty = 0.0
|
|
self.packaging_qty = 0.0
|
|
self.lot_name = False
|
|
|
|
def action_manual_entry(self):
|
|
return True
|
|
|
|
def reset_qty(self):
|
|
self.product_qty = 0
|
|
self.packaging_qty = 0
|
|
|
|
def open_actions(self):
|
|
self.display_menu = True
|
|
return self.env.ref(
|
|
"stock_barcodes.action_stock_barcodes_action_client"
|
|
).read()[0]
|
|
|
|
def action_back(self):
|
|
return self.env.ref("stock.stock_picking_type_action").read()[0]
|
|
|
|
def open_records(self):
|
|
action = self.action_ids
|
|
return action
|
|
|
|
def get_option_value(self, field_name, attribute):
|
|
option = self.option_group_id.option_ids.filtered(
|
|
lambda op: op.field_name == field_name
|
|
)[:1]
|
|
return option[attribute]
|
|
|
|
def action_force_done(self):
|
|
res = self.with_context(force_create_move=True).action_confirm()
|
|
self.visible_force_done = False
|
|
return res
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
wizards = super().create(vals_list)
|
|
for wiz in wizards:
|
|
wiz.action_show_step()
|
|
return wizards
|
|
|
|
def action_manual_quantity(self):
|
|
action = self.get_formview_action()
|
|
form_view = self.env.ref(
|
|
"stock_barcodes.view_stock_barcodes_read_form_manual_qty"
|
|
)
|
|
action["views"] = [(form_view.id, "form")]
|
|
action["res_id"] = self.ids[0]
|
|
return action
|
|
|
|
def action_reopen_wizard(self):
|
|
return self.get_formview_action()
|
|
|
|
@api.onchange("step")
|
|
def action_show_step(self):
|
|
options_required = self.option_group_id.option_ids.filtered("required")
|
|
self.step = 0
|
|
for option in options_required:
|
|
if not getattr(self, option.field_name, False):
|
|
if option.field_name == "lot_id" and self.product_id.tracking == "none":
|
|
continue
|
|
self.step = option.step
|
|
break
|
|
if not self.step:
|
|
self.step = options_required[:1].step
|
|
|
|
options = self.option_group_id.option_ids.filtered(
|
|
lambda op: op.step == self.step and op.to_scan
|
|
)
|
|
self._set_messagge_info(
|
|
"info_page", _("Scan {}").format(", ".join(options.mapped("name")))
|
|
)
|
|
|
|
@api.onchange("package_id")
|
|
def onchange_package_id(self):
|
|
if self.manual_entry:
|
|
self.barcode = self.package_id.name
|
|
self.process_barcode_package_id()
|
|
|
|
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
|
|
no_increase_qty_done, force_create_move = False, False
|
|
context = dict(self.env.context)
|
|
if self._name == "wiz.stock.barcodes.read.picking":
|
|
no_increase_qty_done = (
|
|
context.get("no_increase_qty_done", False) or self.manual_entry
|
|
)
|
|
force_create_move = context.get("force_create_move", False)
|
|
res = self.with_context(
|
|
no_increase_qty_done=no_increase_qty_done,
|
|
force_create_move=force_create_move,
|
|
).action_done()
|
|
self.invalidate_recordset()
|
|
self.play_sounds(res)
|
|
self._set_focus_on_qty_input()
|
|
|
|
if force_create_move:
|
|
# Hide Form Edit
|
|
self.manual_entry = False
|
|
self.send_bus_done(
|
|
"stock_barcodes_scan",
|
|
"stock_barcodes_edit_manual",
|
|
{
|
|
"manual_entry": False,
|
|
},
|
|
)
|
|
|
|
# Count elements for apply in inventory
|
|
if self._name == "wiz.stock.barcodes.read.inventory":
|
|
self.display_read_quant = True
|
|
self._compute_count_inventory_quants()
|
|
self.send_bus_done(
|
|
"stock_barcodes_form_update",
|
|
"count_apply_inventory",
|
|
{"count": self.count_inventory_quants},
|
|
)
|
|
|
|
return res
|
|
|
|
def action_add_scan_manual(self):
|
|
self.manual_entry = True
|
|
self.send_bus_done(
|
|
"stock_barcodes_scan", "stock_barcodes_edit_manual", {"manual_entry": True}
|
|
)
|
|
|
|
def process_lot_before_done(self):
|
|
if (
|
|
not self.lot_id
|
|
and self.lot_name
|
|
and self.product_id
|
|
and self.product_id.tracking != "none"
|
|
and self.option_group_id.create_lot
|
|
):
|
|
self.lot_id = self._create_new_lot()
|
|
return True
|
|
|
|
def play_sounds(self, res):
|
|
if res:
|
|
self.send_bus_done(
|
|
"stock_barcodes_scan",
|
|
"stock_barcodes_sound",
|
|
{"sound": "ok", "res_model": self._name, "res_id": self.ids[0]},
|
|
)
|
|
|
|
else:
|
|
self.send_bus_done(
|
|
"stock_barcodes_scan",
|
|
"stock_barcodes_sound",
|
|
{"sound": "ko", "res_model": self._name, "res_id": self.ids[0]},
|
|
)
|
|
|
|
def _set_focus_on_qty_input(self, field_name=None):
|
|
if field_name is None:
|
|
field_name = "product_qty"
|
|
if field_name == "product_qty" and self.packaging_id:
|
|
field_name = "packaging_qty"
|
|
|
|
self.send_bus_done(
|
|
"stock_barcodes_scan",
|
|
"stock_barcodes_focus",
|
|
{
|
|
"action": "focus",
|
|
"field_name": field_name,
|
|
"res_model": self._name,
|
|
"res_id": self.ids[0],
|
|
},
|
|
)
|
|
|
|
@api.onchange("product_id")
|
|
def onchange_product_id(self):
|
|
self.product_uom_id = self.product_id.uom_id
|
|
|
|
@api.onchange("manual_entry")
|
|
def onchange_manual_entry(self):
|
|
if self.manual_entry and self.option_group_id.manual_entry_field_focus:
|
|
self._set_focus_on_qty_input(self.option_group_id.manual_entry_field_focus)
|
|
|
|
def _prepare_lot_vals(self):
|
|
return {
|
|
"name": self.lot_name,
|
|
"product_id": self.product_id.id,
|
|
"company_id": self.env.company.id,
|
|
}
|
|
|
|
def _create_new_lot(self):
|
|
StockProductionLot = self.env["stock.lot"]
|
|
lot_domain = [
|
|
("name", "=", self.lot_name),
|
|
("product_id", "=", self.product_id.id),
|
|
]
|
|
new_lot = StockProductionLot.search(lot_domain)
|
|
if not new_lot:
|
|
new_lot = StockProductionLot.create(self._prepare_lot_vals())
|
|
return new_lot
|
|
|
|
def action_clean_message(self):
|
|
self.message = False
|
|
self.check_option_required()
|
|
|
|
def action_keep_result_package(self):
|
|
self.keep_result_package = not self.keep_result_package
|
|
|
|
def display_notification(
|
|
self, message, message_type="warning", title=False, sticky=True
|
|
):
|
|
"""Send notifications to web client
|
|
message_type:
|
|
[options.type='warning'] 'info', 'success', 'warning', 'danger' or ''
|
|
sticky: Permanent notification until user removes it
|
|
"""
|
|
if self.option_group_id.display_notification and not self.env.context.get(
|
|
"skip_display_notification", False
|
|
):
|
|
message = {
|
|
"message": message,
|
|
"type": message_type,
|
|
"sticky": sticky,
|
|
"res_model": self._name,
|
|
"res_id": self.ids[0],
|
|
}
|
|
if title:
|
|
message["title"] = title
|
|
self.send_bus_done(
|
|
"stock_barcodes-{}".format(self.ids[0]),
|
|
"stock_barcodes_notify-{}".format(self.ids[0]),
|
|
message,
|
|
)
|