Skip to content

Commit

Permalink
[ADD] allows to create zero quants for products based on the inventor…
Browse files Browse the repository at this point in the history
…y configuration to avoid having no smartbutton on the adjustment form
  • Loading branch information
benwillig committed Jul 16, 2024
1 parent bbe013f commit 5db8b82
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 35 deletions.
4 changes: 4 additions & 0 deletions stock_inventory/models/res_company.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ class ResCompany(models.Model):
"are done, the adjustment is automatically set to done.",
default=False,
)
stock_inventory_auto_create_missing_quants = fields.Boolean(
help="If enabled, missing quants will be created on the inventory confirmation",
default=False,
)
3 changes: 3 additions & 0 deletions stock_inventory/models/res_config_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ class ResConfigSettings(models.TransientModel):
stock_inventory_auto_complete = fields.Boolean(
related="company_id.stock_inventory_auto_complete", readonly=False
)
stock_inventory_auto_create_missing_quants = fields.Boolean(
related="company_id.stock_inventory_auto_create_missing_quants", readonly=False
)
174 changes: 171 additions & 3 deletions stock_inventory/models/stock_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ class InventoryAdjustmentsGroup(models.Model):
help="Specific responsible of Inventory Adjustment.",
)

auto_create_missing_quants = fields.Boolean(
string="Create missing quants",
readonly=True,
states=READONLY_STATES,
default=False,
)

@api.depends("stock_quant_ids")
def _compute_count_stock_quants(self):
for rec in self:
Expand Down Expand Up @@ -179,6 +186,13 @@ def _compute_action_state_to_cancel_allowed(self):
for rec in self:
rec.action_state_to_cancel_allowed = rec.state == "draft"

@api.onchange("company_id")
def _onchange_load_auto_create_missing_quants(self):
for rec in self:
rec.auto_create_missing_quants = (
rec.company_id.stock_inventory_auto_create_missing_quants
)

def _get_quants(self, locations):
self.ensure_one()
domain = []
Expand All @@ -195,6 +209,40 @@ def _get_quants(self, locations):
domain = self._get_domain_category_quants(base_domain)
return self.env["stock.quant"].search(domain)

def _get_or_create_quants(self):
"""
Create or retrieve stock.quant for the given inventory configuration
:return: stock.quant
"""
self.ensure_one()
quant_ids = []
for quant_values in self._get_quants_values():
quant = self._get_quant(quant_values)
quant_ids.append(quant.id)
return self.env["stock.quant"].browse(quant_ids)

def _get_quant(self, quant_values):
"""
Get an existing or create a new quant based on preloaded quant values
product_id, lot_id and location_id must be present.
:param quant_values:
:return:
"""
self.ensure_one()
Quant = self.env["stock.quant"]

quant = Quant.search(
[
("product_id", "=", quant_values.get("product_id")),
("lot_id", "=", quant_values.get("lot_id")),
("location_id", "=", quant_values.get("location_id")),
],
limit=1,
)
if not quant:
quant = Quant.create(quant_values)
return quant

def _get_base_domain(self, locations):
return (
[
Expand Down Expand Up @@ -258,7 +306,7 @@ def refresh_stock_quant_ids(self):
def _get_quant_joined_names(self, quants, field):
return ", ".join(quants.mapped(f"{field}.display_name"))

def action_state_to_in_progress(self):
def _check_existing_in_progress_inventory(self):
self.ensure_one()
search_filter = [
(
Expand Down Expand Up @@ -286,6 +334,11 @@ def action_state_to_in_progress(self):
names = self._get_quant_joined_names(quants, error_field)
raise ValidationError(error_message % names)

def action_state_to_in_progress(self):
self.ensure_one()
self._check_existing_in_progress_inventory()
if self.auto_create_missing_quants:
self._get_or_create_quants()
quants = self._get_quants(self.location_ids)
self.write(
{
Expand All @@ -296,7 +349,7 @@ def action_state_to_in_progress(self):
quants.write(
{
"to_do": True,
"user_id": self.responsible_id,
"user_id": self.responsible_id.id or self.env.user.id,
"inventory_date": self.date,
}
)
Expand Down Expand Up @@ -361,11 +414,12 @@ def action_view_inventory_adjustment(self):
"search_default_to_do": 1,
"inventory_id": self.id,
"default_to_do": True,
"default_user_id": self.env.user.id,
}
)
result.update(
{
"domain": [("id", "in", self.stock_quant_ids.ids)],
"domain": [("stock_inventory_ids", "in", self.ids)],
"search_view_id": self.env.ref("stock.quant_search_view").id,
"context": context,
}
Expand Down Expand Up @@ -432,3 +486,117 @@ def _check_one_product_in_product_selection(self):
" you are only able to add one product."
)
)

def _get_inventory_product_domain(self):
self.ensure_one()
product_selection = self.product_selection
domain = []
if product_selection == "all":
domain = self._get_inventory_product_domain_all()
elif product_selection == "manual":
domain = self._get_inventory_product_domain_manual()
elif product_selection == "one":
domain = self._get_inventory_product_domain_one()
elif product_selection == "lot":
domain = self._get_inventory_product_domain_lot()
elif product_selection == "category":
domain = self._get_inventory_product_domain_category()
return expression.AND(
[
[
("type", "=", "product"),
],
domain,
]
)

def _get_inventory_product_domain_all(self):
self.ensure_one()
return [
("type", "=", "product"),
]

def _get_inventory_product_domain_manual(self):
self.ensure_one()
return [
("id", "in", self.product_ids.ids),
]

def _get_inventory_product_domain_one(self):
self.ensure_one()
return [
("id", "in", self.product_ids.ids),
]

def _get_inventory_product_domain_lot(self):
self.ensure_one()
return [
("id", "in", self.product_ids.ids),
]

def _get_inventory_product_domain_category(self):
self.ensure_one()
category = self.category_id
return [
"|",
("categ_id", "=", category.id),
("categ_id", "in", category.child_id.ids),
]

def _get_quants_values(self):
self.ensure_one()
product_domain = self._get_inventory_product_domain()
products = self.env["product.product"].search(product_domain)
inv_lots = self.lot_ids
quants_values = []
locations = self._get_locations()
for product in products:
if product.tracking in ("lot", "serial"):
product_lots = self._get_product_lots(product)
for lot in product_lots:
if inv_lots and lot not in inv_lots:
continue
quants_values.extend(
self._get_new_quants_values(product, locations, lot_id=lot.id)
)
else:
quants_values.extend(self._get_new_quants_values(product, locations))
return quants_values

def _get_new_quant_base_values(self, product, **kwargs):
self.ensure_one()
values = {
"product_id": product.id,
"user_id": self.env.user.id,
"stock_inventory_ids": [(4, self.id)],
}
values.update(kwargs)
return values

def _get_new_quants_values(self, product, locations, **kwargs):
base_values = self._get_new_quant_base_values(product, **kwargs)
quants_values = []
for location in locations:
values = base_values.copy()
values["location_id"] = location.id
quants_values.append(values)
return quants_values

def _get_locations(self):
self.ensure_one()
locations = self.location_ids
if self.exclude_sublocation:
domain = [("id", "in", locations.ids)]
else:
domain = [
("location_id", "child_of", locations.child_internal_location_ids.ids)
]
return self.env["stock.location"].search(domain)

def _get_product_lots(self, product):
self.ensure_one()
return self.env["stock.lot"].search(
[
("product_id", "=", product.id),
]
)
80 changes: 49 additions & 31 deletions stock_inventory/models/stock_quant.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,89 @@
from odoo import _, api, fields, models
from odoo import fields, models


class StockQuant(models.Model):
_inherit = "stock.quant"

to_do = fields.Boolean(default=False)
virtual_in_progress_inventory_id = fields.Many2one(
comodel_name="stock.inventory",
compute="_compute_virtual_in_progress_inventory_id",
)
stock_inventory_ids = fields.Many2many(
comodel_name="stock.inventory",
string="Inventory Adjustment",
readonly=True,
)

def _compute_virtual_in_progress_inventory_id(self):
Inventory = self.env["stock.inventory"]
for rec in self:
rec.virtual_in_progress_inventory_id = Inventory.search(
[
("state", "=", "in_progress"),
"|",
"&",
("exclude_sublocation", "=", False),
("location_ids", "parent_of", rec.location_id.ids),
"&",
("exclude_sublocation", "=", True),
("location_ids", "in", rec.location_id.ids),
],
limit=1,
)

def _apply_inventory(self):
res = super()._apply_inventory()
record_moves = self.env["stock.move.line"]
adjustment = self.env["stock.inventory"].browse()
for rec in self:
adjustment = (
self.env["stock.inventory"]
.search([("state", "=", "in_progress")])
.filtered(
lambda x: rec.location_id in x.location_ids
or (
rec.location_id in x.location_ids.child_internal_location_ids
and not x.exclude_sublocation
)
)
)
moves = record_moves.search(
adjustment = rec.virtual_in_progress_inventory_id
move = record_moves.search(
[
("id", "in", adjustment.stock_move_ids.ids),
("product_id", "=", rec.product_id.id),
("lot_id", "=", rec.lot_id.id),
"|",
("location_id", "=", rec.location_id.id),
("location_dest_id", "=", rec.location_id.id),
],
order="create_date asc",
limit=1,
).filtered(
lambda x: not x.company_id.id
or not rec.company_id.id
or rec.company_id.id == x.company_id.id
)
if len(moves) == 0:
raise ValueError(_("No move lines have been created"))
move = moves[len(moves) - 1]
adjustment.stock_move_ids |= move
if not move:
continue
reference = move.reference
if adjustment.name and move.reference:
reference = adjustment.name + ": " + move.reference
elif adjustment.name:
reference = adjustment.name
move.write(
{
"inventory_adjustment_id": adjustment.id,
"reference": reference,
}
)
rec.to_do = False
if adjustment and self.env.company.stock_inventory_auto_complete:
if adjustment and adjustment.company_id.stock_inventory_auto_complete:
adjustment.action_auto_state_to_done()
return res

def _get_inventory_move_values(self, qty, location_id, location_dest_id, out=False):
res = super()._get_inventory_move_values(
qty, location_id, location_dest_id, out=out
)
inventory = self.virtual_in_progress_inventory_id
if self.virtual_in_progress_inventory_id:
for move_line_item in res.get("move_line_ids", []):
move_line_values = move_line_item[-1]
move_line_values.update(
{
"inventory_adjustment_id": inventory.id,
}
)
return res

def _get_inventory_fields_write(self):
return super()._get_inventory_fields_write() + ["to_do"]

@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
if self.env.context.get(
"active_model", False
) == "stock.inventory" and self.env.context.get("active_id", False):
self.env["stock.inventory"].browse(
self.env.context.get("active_id")
).refresh_stock_quant_ids()
return res
Loading

0 comments on commit 5db8b82

Please sign in to comment.