diff --git a/stock_inventory/models/stock_inventory.py b/stock_inventory/models/stock_inventory.py
index d4336efd52e3..a15dfe34de76 100644
--- a/stock_inventory/models/stock_inventory.py
+++ b/stock_inventory/models/stock_inventory.py
@@ -1,25 +1,32 @@
from odoo import _, api, fields, models
-from odoo.exceptions import ValidationError
+from odoo.exceptions import UserError, ValidationError
from odoo.osv import expression
+READONLY_STATES = {
+ "draft": [("readonly", False)],
+}
+
class InventoryAdjustmentsGroup(models.Model):
_name = "stock.inventory"
_description = "Inventory Adjustment Group"
_order = "date desc, id desc"
+ _inherit = [
+ "mail.thread",
+ ]
name = fields.Char(
required=True,
default="Inventory",
string="Inventory Reference",
- states={"draft": [("readonly", False)]},
readonly=True,
+ states=READONLY_STATES,
)
date = fields.Datetime(
default=lambda self: fields.Datetime.now(),
- states={"draft": [("readonly", False)]},
readonly=True,
+ states=READONLY_STATES,
)
company_id = fields.Many2one(
@@ -32,12 +39,22 @@ class InventoryAdjustmentsGroup(models.Model):
)
state = fields.Selection(
- [("draft", "Draft"), ("in_progress", "In Progress"), ("done", "Done")],
+ [
+ ("draft", "Draft"),
+ ("in_progress", "In Progress"),
+ ("done", "Done"),
+ ("cancel", "Cancelled"),
+ ],
default="draft",
+ tracking=True,
)
owner_id = fields.Many2one(
- "res.partner", "Owner", help="This is the owner of the inventory adjustment"
+ "res.partner",
+ "Owner",
+ help="This is the owner of the inventory adjustment",
+ readonly=True,
+ states=READONLY_STATES,
)
location_ids = fields.Many2many(
@@ -45,6 +62,8 @@ class InventoryAdjustmentsGroup(models.Model):
string="Locations",
domain="[('usage', '=', 'internal'), "
"'|', ('company_id', '=', company_id), ('company_id', '=', False)]",
+ readonly=True,
+ states=READONLY_STATES,
)
product_selection = fields.Selection(
@@ -57,32 +76,47 @@ class InventoryAdjustmentsGroup(models.Model):
],
default="all",
required=True,
+ readonly=True,
+ states=READONLY_STATES,
)
product_ids = fields.Many2many(
"product.product",
string="Products",
domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]",
+ readonly=True,
+ states=READONLY_STATES,
)
stock_quant_ids = fields.Many2many(
"stock.quant",
string="Inventory Adjustment",
domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]",
+ readonly=True,
+ states=READONLY_STATES,
)
- category_id = fields.Many2one("product.category", string="Product Category")
+ category_id = fields.Many2one(
+ "product.category",
+ string="Product Category",
+ readonly=True,
+ states=READONLY_STATES,
+ )
lot_ids = fields.Many2many(
"stock.lot",
string="Lot/Serial Numbers",
domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]",
+ readonly=True,
+ states=READONLY_STATES,
)
stock_move_ids = fields.One2many(
"stock.move.line",
"inventory_adjustment_id",
string="Inventory Adjustments Done",
+ readonly=True,
+ states=READONLY_STATES,
)
count_stock_quants = fields.Integer(
@@ -96,6 +130,9 @@ class InventoryAdjustmentsGroup(models.Model):
count_stock_moves = fields.Integer(
compute="_compute_count_stock_moves", string="Stock Moves Lines"
)
+ action_state_to_cancel_allowed = fields.Boolean(
+ compute="_compute_action_state_to_cancel_allowed"
+ )
exclude_sublocation = fields.Boolean(
help="If enabled, it will only take into account "
@@ -112,18 +149,38 @@ class InventoryAdjustmentsGroup(models.Model):
@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.filtered(lambda sq: sq.to_do))
- self.count_stock_quants_string = "{} / {}".format(
- count_todo, self.count_stock_quants
- )
+ for rec in self:
+ quants = rec.stock_quant_ids
+ quants_to_do = quants.filtered(lambda q: q.to_do)
+ count_todo = len(quants_to_do)
+ rec.count_stock_quants = len(quants)
+ rec.count_stock_quants_string = "{} / {}".format(
+ count_todo, rec.count_stock_quants
+ )
@api.depends("stock_move_ids")
def _compute_count_stock_moves(self):
- sm_ids = self.mapped("stock_move_ids").ids
- self.count_stock_moves = len(sm_ids)
+ group_fname = "inventory_adjustment_id"
+ group_data = self.env["stock.move.line"].read_group(
+ [
+ (group_fname, "in", self.ids),
+ ],
+ [group_fname],
+ [group_fname],
+ )
+ data_by_adj_id = {
+ row[group_fname][0]: row.get(f"{group_fname}_count", 0)
+ for row in group_data
+ }
+ for rec in self:
+ rec.count_stock_moves = data_by_adj_id.get(rec.id, 0)
+
+ def _compute_action_state_to_cancel_allowed(self):
+ for rec in self:
+ rec.action_state_to_cancel_allowed = rec.state == "draft"
def _get_quants(self, locations):
+ self.ensure_one()
domain = []
base_domain = self._get_base_domain(locations)
if self.product_selection == "all":
@@ -153,11 +210,13 @@ def _get_domain_all_quants(self, base_domain):
return base_domain
def _get_domain_manual_quants(self, base_domain):
+ self.ensure_one()
return expression.AND(
[base_domain, [("product_id", "in", self.product_ids.ids)]]
)
def _get_domain_one_quant(self, base_domain):
+ self.ensure_one()
return expression.AND(
[
base_domain,
@@ -168,6 +227,7 @@ def _get_domain_one_quant(self, base_domain):
)
def _get_domain_lot_quants(self, base_domain):
+ self.ensure_one()
return expression.AND(
[
base_domain,
@@ -179,6 +239,7 @@ def _get_domain_lot_quants(self, base_domain):
)
def _get_domain_category_quants(self, base_domain):
+ self.ensure_one()
return expression.AND(
[
base_domain,
@@ -195,6 +256,7 @@ def refresh_stock_quant_ids(self):
rec.stock_quant_ids = rec._get_quants(rec.location_ids)
def action_state_to_in_progress(self):
+ self.ensure_one()
active_rec = self.env["stock.inventory"].search(
[
("state", "=", "in_progress"),
@@ -203,15 +265,20 @@ def action_state_to_in_progress(self):
limit=1,
)
if active_rec:
- raise ValidationError(
+ raise UserError(
_(
"There's already an Adjustment in Process using one requested Location: %s"
)
% active_rec.name
)
- self.state = "in_progress"
- self.refresh_stock_quant_ids()
- self.stock_quant_ids.update(
+ quants = self._get_quants(self.location_ids)
+ self.write(
+ {
+ "state": "in_progress",
+ "stock_quant_ids": [(6, 0, quants.ids)],
+ }
+ )
+ quants.write(
{
"to_do": True,
"user_id": self.responsible_id,
@@ -221,6 +288,7 @@ def action_state_to_in_progress(self):
return
def action_state_to_done(self):
+ self.ensure_one()
self.state = "done"
self.stock_quant_ids.update(
{
@@ -238,6 +306,7 @@ def action_auto_state_to_done(self):
return
def action_state_to_draft(self):
+ self.ensure_one()
self.state = "draft"
self.stock_quant_ids.update(
{
@@ -249,20 +318,52 @@ def action_state_to_draft(self):
self.stock_quant_ids = None
return
+ def action_state_to_cancel(self):
+ self.ensure_one()
+ self._check_action_state_to_cancel()
+ self.write(
+ {
+ "state": "cancel",
+ }
+ )
+
+ def _check_action_state_to_cancel(self):
+ for rec in self:
+ if not rec.action_state_to_cancel_allowed:
+ raise UserError(
+ _(
+ "You can't cancel this inventory %(display_name)s.",
+ display_name=rec.display_name,
+ )
+ )
+
def action_view_inventory_adjustment(self):
+ self.ensure_one()
result = self.env["stock.quant"].action_view_inventory()
- 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
+ context = result.get("context", {})
+ context.update(
+ {
+ "search_default_to_do": 1,
+ "inventory_id": self.id,
+ "default_to_do": True,
+ }
+ )
+ result.update(
+ {
+ "domain": [("id", "in", self.stock_quant_ids.ids)],
+ "search_view_id": self.env.ref("stock.quant_search_view").id,
+ "context": context,
+ }
+ )
return result
def action_view_stock_moves(self):
+ self.ensure_one()
result = self.env["ir.actions.act_window"]._for_xml_id(
"stock_inventory.action_view_stock_move_line_inventory_tree"
)
- sm_ids = self.mapped("stock_move_ids").ids
- result["domain"] = [("id", "in", sm_ids)]
- result["context"] = []
+ result["domain"] = [("inventory_adjustment_id", "=", self.id)]
+ result["context"] = {}
return result
@api.constrains("state", "location_ids")
diff --git a/stock_inventory/models/stock_quant.py b/stock_inventory/models/stock_quant.py
index f63a7d340135..6207b8a5aa0b 100644
--- a/stock_inventory/models/stock_quant.py
+++ b/stock_inventory/models/stock_quant.py
@@ -59,9 +59,9 @@ def _apply_inventory(self):
def _get_inventory_fields_write(self):
return super()._get_inventory_fields_write() + ["to_do"]
- @api.model
- def create(self, vals):
- res = super().create(vals)
+ @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):
diff --git a/stock_inventory/tests/test_stock_inventory.py b/stock_inventory/tests/test_stock_inventory.py
index 24ce73aa25f0..3219b2ea2de7 100644
--- a/stock_inventory/tests/test_stock_inventory.py
+++ b/stock_inventory/tests/test_stock_inventory.py
@@ -1,7 +1,7 @@
# Copyright 2022 ForgeFlow S.L
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
-from odoo.exceptions import ValidationError
+from odoo.exceptions import UserError, ValidationError
from odoo.tests.common import TransactionCase
@@ -121,7 +121,7 @@ def test_01_all_locations(self):
"location_ids": [self.location1.id],
}
)
- with self.assertRaises(ValidationError), self.cr.savepoint():
+ with self.assertRaises(UserError), self.cr.savepoint():
inventory2.action_state_to_in_progress()
self.assertEqual(inventory1.state, "in_progress")
self.assertEqual(
@@ -137,7 +137,7 @@ def test_01_all_locations(self):
inventory1.action_view_inventory_adjustment()
self.quant1.inventory_quantity = 92
self.quant1.action_apply_inventory()
- inventory1._compute_count_stock_quants()
+ inventory1.invalidate_recordset()
inventory1.action_view_stock_moves()
self.assertEqual(inventory1.count_stock_moves, 1)
self.assertEqual(inventory1.count_stock_quants, 3)
@@ -172,8 +172,7 @@ def test_02_manual_selection(self):
inventory1.action_view_inventory_adjustment()
self.quant3.inventory_quantity = 74
self.quant3.action_apply_inventory()
- inventory1._compute_count_stock_quants()
- inventory1.action_view_stock_moves()
+ inventory1.invalidate_recordset()
self.assertEqual(inventory1.count_stock_moves, 1)
self.assertEqual(inventory1.count_stock_quants, 2)
self.assertEqual(inventory1.count_stock_quants_string, "1 / 2")
@@ -183,15 +182,15 @@ def test_02_manual_selection(self):
self.assertEqual(inventory1.stock_move_ids.location_id.id, self.location3.id)
self.quant1.inventory_quantity = 65
self.quant1.action_apply_inventory()
- inventory1._compute_count_stock_quants()
+ inventory1.invalidate_recordset()
self.assertEqual(inventory1.count_stock_moves, 2)
self.assertEqual(inventory1.count_stock_quants, 2)
self.assertEqual(inventory1.count_stock_quants_string, "0 / 2")
inventory1.action_state_to_done()
def test_03_one_selection(self):
- with self.assertRaises(ValidationError), self.cr.savepoint():
- inventory1 = self.inventory_model.create(
+ with self.assertRaises(UserError), self.cr.savepoint():
+ self.inventory_model.create(
{
"name": "Inventory_Test_5",
"product_selection": "one",
@@ -222,7 +221,7 @@ def test_03_one_selection(self):
inventory1.action_view_inventory_adjustment()
self.quant3.inventory_quantity = 74
self.quant3.action_apply_inventory()
- inventory1._compute_count_stock_quants()
+ inventory1.invalidate_recordset()
inventory1.action_view_stock_moves()
self.assertEqual(inventory1.count_stock_moves, 1)
self.assertEqual(inventory1.count_stock_quants, 2)
@@ -233,15 +232,15 @@ def test_03_one_selection(self):
self.assertEqual(inventory1.stock_move_ids.location_id.id, self.location3.id)
self.quant1.inventory_quantity = 65
self.quant1.action_apply_inventory()
- inventory1._compute_count_stock_quants()
+ inventory1.invalidate_recordset()
self.assertEqual(inventory1.count_stock_moves, 2)
self.assertEqual(inventory1.count_stock_quants, 2)
self.assertEqual(inventory1.count_stock_quants_string, "0 / 2")
inventory1.action_state_to_done()
def test_04_lot_selection(self):
- with self.assertRaises(ValidationError), self.cr.savepoint():
- inventory1 = self.inventory_model.create(
+ with self.assertRaises(UserError), self.cr.savepoint():
+ self.inventory_model.create(
{
"name": "Inventory_Test_6",
"product_selection": "lot",
@@ -272,7 +271,7 @@ def test_04_lot_selection(self):
inventory1.action_view_inventory_adjustment()
self.quant3.inventory_quantity = 74
self.quant3.action_apply_inventory()
- inventory1._compute_count_stock_quants()
+ inventory1.invalidate_recordset()
inventory1.action_view_stock_moves()
self.assertEqual(inventory1.count_stock_moves, 1)
self.assertEqual(inventory1.count_stock_quants, 1)
@@ -306,7 +305,7 @@ def test_05_category_selection(self):
inventory1.action_view_inventory_adjustment()
self.quant4.inventory_quantity = 74
self.quant4.action_apply_inventory()
- inventory1._compute_count_stock_quants()
+ inventory1.invalidate_recordset()
inventory1.action_view_stock_moves()
self.assertEqual(inventory1.count_stock_moves, 1)
self.assertEqual(inventory1.count_stock_quants, 1)
@@ -334,7 +333,7 @@ def test_06_exclude_sub_locations(self):
"exclude_sublocation": True,
}
)
- with self.assertRaises(ValidationError), self.cr.savepoint():
+ with self.assertRaises(UserError), self.cr.savepoint():
inventory2.action_state_to_in_progress()
self.assertEqual(inventory1.state, "in_progress")
self.assertEqual(
diff --git a/stock_inventory/views/stock_inventory.xml b/stock_inventory/views/stock_inventory.xml
index fed3e75b6602..a4394b587530 100644
--- a/stock_inventory/views/stock_inventory.xml
+++ b/stock_inventory/views/stock_inventory.xml
@@ -20,6 +20,13 @@
attrs="{'invisible':['|',('state', 'in', ['draft', 'done']), ('count_stock_moves', '!=', 0)]}"
string="Back to Draft"
/>
+
+