diff --git a/stock_inventory/__manifest__.py b/stock_inventory/__manifest__.py
index 16fa4a782e90..2121d8d1bb7a 100644
--- a/stock_inventory/__manifest__.py
+++ b/stock_inventory/__manifest__.py
@@ -11,9 +11,11 @@
"depends": ["stock"],
"data": [
"security/ir.model.access.csv",
+ "security/security.xml",
"views/stock_inventory.xml",
"views/stock_quant.xml",
"views/stock_move_line.xml",
+ "views/res_config_settings_view.xml",
],
"installable": True,
"application": False,
diff --git a/stock_inventory/models/__init__.py b/stock_inventory/models/__init__.py
index 09732e334e94..b7114e34a3db 100644
--- a/stock_inventory/models/__init__.py
+++ b/stock_inventory/models/__init__.py
@@ -1,3 +1,5 @@
from . import stock_inventory
from . import stock_quant
from . import stock_move_line
+from . import res_company
+from . import res_config_settings
diff --git a/stock_inventory/models/res_company.py b/stock_inventory/models/res_company.py
new file mode 100644
index 000000000000..1181c7cc9990
--- /dev/null
+++ b/stock_inventory/models/res_company.py
@@ -0,0 +1,15 @@
+# Copyright 2024 ForgeFlow S.L. (http://www.forgeflow.com)
+# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
+
+
+from odoo import fields, models
+
+
+class ResCompany(models.Model):
+ _inherit = "res.company"
+
+ stock_inventory_auto_complete = fields.Boolean(
+ help="If enabled, when all the quants prepared for the adjustment "
+ "are done, the adjustment is automatically set to done.",
+ default=False,
+ )
diff --git a/stock_inventory/models/res_config_settings.py b/stock_inventory/models/res_config_settings.py
new file mode 100644
index 000000000000..3817c268b445
--- /dev/null
+++ b/stock_inventory/models/res_config_settings.py
@@ -0,0 +1,12 @@
+# Copyright 2024 ForgeFlow S.L. (http://www.forgeflow.com)
+# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
+
+from odoo import fields, models
+
+
+class ResConfigSettings(models.TransientModel):
+ _inherit = "res.config.settings"
+
+ stock_inventory_auto_complete = fields.Boolean(
+ related="company_id.stock_inventory_auto_complete", readonly=False
+ )
diff --git a/stock_inventory/models/stock_inventory.py b/stock_inventory/models/stock_inventory.py
index 8e96d63fbc16..ec963ef6f227 100644
--- a/stock_inventory/models/stock_inventory.py
+++ b/stock_inventory/models/stock_inventory.py
@@ -8,9 +8,28 @@ class InventoryAdjustmentsGroup(models.Model):
_description = "Inventory Adjustment Group"
_order = "date desc, id desc"
- name = fields.Char(required=True, default="Inventory", string="Inventory Reference")
+ name = fields.Char(
+ required=True,
+ default="Inventory",
+ string="Inventory Reference",
+ states={"draft": [("readonly", False)]},
+ readonly=True,
+ )
- date = fields.Datetime(default=lambda self: fields.Datetime.now())
+ date = fields.Datetime(
+ default=lambda self: fields.Datetime.now(),
+ states={"draft": [("readonly", False)]},
+ readonly=True,
+ )
+
+ company_id = fields.Many2one(
+ comodel_name="res.company",
+ readonly=True,
+ index=True,
+ states={"draft": [("readonly", False)]},
+ default=lambda self: self.env.company,
+ required=True,
+ )
state = fields.Selection(
[("draft", "Draft"), ("in_progress", "In Progress"), ("done", "Done")],
@@ -22,7 +41,10 @@ class InventoryAdjustmentsGroup(models.Model):
)
location_ids = fields.Many2many(
- "stock.location", string="Locations", domain="[('usage', '=', 'internal')]"
+ "stock.location",
+ string="Locations",
+ domain="[('usage', '=', 'internal'), "
+ "'|', ('company_id', '=', company_id), ('company_id', '=', False)]",
)
product_selection = fields.Selection(
@@ -37,15 +59,24 @@ class InventoryAdjustmentsGroup(models.Model):
required=True,
)
- product_ids = fields.Many2many("product.product", string="Products")
+ product_ids = fields.Many2many(
+ "product.product",
+ string="Products",
+ domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]",
+ )
- stock_quant_ids = fields.Many2many("stock.quant", string="Inventory Adjustment")
+ stock_quant_ids = fields.Many2many(
+ "stock.quant",
+ string="Inventory Adjustment",
+ domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]",
+ )
category_id = fields.Many2one("product.category", string="Product Category")
lot_ids = fields.Many2many(
"stock.production.lot",
string="Lot/Serial Numbers",
+ domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]",
)
stock_move_ids = fields.One2many(
@@ -66,14 +97,24 @@ class InventoryAdjustmentsGroup(models.Model):
compute="_compute_count_stock_moves", string="Stock Moves Lines"
)
+ exclude_sublocation = fields.Boolean(
+ help="If enabled, it will only take into account "
+ "the locations selected, and not their children."
+ )
+
+ responsible_id = fields.Many2one(
+ comodel_name="res.users",
+ string="Assigned to",
+ states={"draft": [("readonly", False)]},
+ readonly=True,
+ tracking=True,
+ help="Specific responsible of Inventory Adjustment.",
+ )
+
@api.depends("stock_quant_ids")
def _compute_count_stock_quants(self):
self.count_stock_quants = len(self.stock_quant_ids)
- count_todo = len(
- self.stock_quant_ids.search(
- [("id", "in", self.stock_quant_ids.ids), ("to_do", "=", "True")]
- )
- )
+ count_todo = len(self.stock_quant_ids.filtered(lambda sq: sq.to_do))
self.count_stock_quants_string = "{} / {}".format(
count_todo, self.count_stock_quants
)
@@ -99,11 +140,15 @@ def _get_quants(self, locations):
return self.env["stock.quant"].search(domain)
def _get_base_domain(self, locations):
- return [
- "|",
- ("location_id", "in", locations.mapped("id")),
- ("location_id", "in", locations.child_ids.ids),
- ]
+ return (
+ [
+ ("location_id", "in", locations.mapped("id")),
+ ]
+ if self.exclude_sublocation
+ else [
+ ("location_id", "child_of", locations.child_internal_location_ids.ids),
+ ]
+ )
def _get_domain_all_quants(self, base_domain):
return base_domain
@@ -146,13 +191,15 @@ def _get_domain_category_quants(self, base_domain):
]
)
+ def refresh_stock_quant_ids(self):
+ for rec in self:
+ rec.stock_quant_ids = rec._get_quants(rec.location_ids)
+
def action_state_to_in_progress(self):
active_rec = self.env["stock.inventory"].search(
[
("state", "=", "in_progress"),
- "|",
- ("location_ids", "in", self.location_ids.mapped("id")),
- ("location_ids", "in", self.location_ids.child_ids.ids),
+ ("location_ids", "child_of", self.location_ids.ids),
],
limit=1,
)
@@ -164,25 +211,48 @@ def action_state_to_in_progress(self):
% active_rec.name
)
self.state = "in_progress"
- self.stock_quant_ids = self._get_quants(self.location_ids)
- self.stock_quant_ids.update({"to_do": True})
+ self.refresh_stock_quant_ids()
+ self.stock_quant_ids.update(
+ {
+ "to_do": True,
+ "user_id": self.responsible_id,
+ "inventory_date": self.date,
+ }
+ )
return
def action_state_to_done(self):
self.state = "done"
- self.stock_quant_ids.update({"to_do": True})
+ self.stock_quant_ids.update(
+ {
+ "to_do": True,
+ "user_id": False,
+ "inventory_date": False,
+ }
+ )
+ return
+
+ def action_auto_state_to_done(self):
+ self.ensure_one()
+ if not any(self.stock_quant_ids.filtered(lambda sq: sq.to_do)):
+ self.action_state_to_done()
return
def action_state_to_draft(self):
self.state = "draft"
- self.stock_quant_ids.update({"to_do": True})
+ self.stock_quant_ids.update(
+ {
+ "to_do": True,
+ "user_id": False,
+ "inventory_date": False,
+ }
+ )
self.stock_quant_ids = None
return
def action_view_inventory_adjustment(self):
result = self.env["stock.quant"].action_view_inventory()
- ia_ids = self.mapped("stock_quant_ids").ids
- result["domain"] = [("id", "in", ia_ids)]
+ result["domain"] = [("id", "in", self.stock_quant_ids.ids)]
result["search_view_id"] = self.env.ref("stock.quant_search_view").id
result["context"]["search_default_to_do"] = 1
return result
@@ -196,6 +266,31 @@ def action_view_stock_moves(self):
result["context"] = []
return result
+ @api.constrains("state", "location_ids")
+ def _check_inventory_in_progress_not_override(self):
+ inventories = self.search([("state", "=", "in_progress")])
+ for rec in inventories:
+ inventory = inventories.filtered(
+ lambda x: x.id != rec.id
+ and (
+ any(i in x.location_ids for i in rec.location_ids)
+ or (
+ any(
+ i in x.location_ids.child_internal_location_ids
+ for i in rec.location_ids
+ )
+ and not x.exclude_sublocation
+ )
+ )
+ )
+ if len(inventory) > 0:
+ raise ValidationError(
+ _(
+ "Cannot be more than one in progress inventory adjustment "
+ "affecting the same location at the same time."
+ )
+ )
+
@api.constrains("product_selection", "product_ids")
def _check_one_product_in_product_selection(self):
for rec in self:
diff --git a/stock_inventory/models/stock_quant.py b/stock_inventory/models/stock_quant.py
index bea534f330b6..bfd04a6c4b99 100644
--- a/stock_inventory/models/stock_quant.py
+++ b/stock_inventory/models/stock_quant.py
@@ -1,4 +1,4 @@
-from odoo import _, fields, models
+from odoo import _, api, fields, models
class StockQuant(models.Model):
@@ -15,7 +15,10 @@ def _apply_inventory(self):
.search([("state", "=", "in_progress")])
.filtered(
lambda x: rec.location_id in x.location_ids
- or rec.location_id in x.location_ids.child_ids
+ or (
+ rec.location_id in x.location_ids.child_internal_location_ids
+ and not x.exclude_sublocation
+ )
)
)
moves = record_moves.search(
@@ -36,9 +39,32 @@ def _apply_inventory(self):
raise ValueError(_("No move lines have been created"))
move = moves[len(moves) - 1]
adjustment.stock_move_ids |= move
- move.inventory_adjustment_id = adjustment
+ 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 self.env.company.stock_inventory_auto_complete:
+ adjustment.action_auto_state_to_done()
return res
def _get_inventory_fields_write(self):
return super()._get_inventory_fields_write() + ["to_do"]
+
+ @api.model
+ def create(self, vals):
+ res = super().create(vals)
+ 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
diff --git a/stock_inventory/security/security.xml b/stock_inventory/security/security.xml
new file mode 100644
index 000000000000..11aab9b291b1
--- /dev/null
+++ b/stock_inventory/security/security.xml
@@ -0,0 +1,11 @@
+
+