Merge PR #2017 into 16.0

Signed-off-by LoisRForgeFlow
This commit is contained in:
OCA-git-bot
2024-06-27 12:17:13 +00:00
4 changed files with 169 additions and 51 deletions

View File

@@ -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 @@ class InventoryAdjustmentsGroup(models.Model):
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 @@ class InventoryAdjustmentsGroup(models.Model):
)
def _get_domain_lot_quants(self, base_domain):
self.ensure_one()
return expression.AND(
[
base_domain,
@@ -179,6 +239,7 @@ class InventoryAdjustmentsGroup(models.Model):
)
def _get_domain_category_quants(self, base_domain):
self.ensure_one()
return expression.AND(
[
base_domain,
@@ -195,6 +256,7 @@ class InventoryAdjustmentsGroup(models.Model):
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 @@ class InventoryAdjustmentsGroup(models.Model):
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 @@ class InventoryAdjustmentsGroup(models.Model):
return
def action_state_to_done(self):
self.ensure_one()
self.state = "done"
self.stock_quant_ids.update(
{
@@ -238,6 +306,7 @@ class InventoryAdjustmentsGroup(models.Model):
return
def action_state_to_draft(self):
self.ensure_one()
self.state = "draft"
self.stock_quant_ids.update(
{
@@ -249,20 +318,52 @@ class InventoryAdjustmentsGroup(models.Model):
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")

View File

@@ -59,9 +59,9 @@ class StockQuant(models.Model):
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):

View File

@@ -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 @@ class TestStockInventory(TransactionCase):
"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 @@ class TestStockInventory(TransactionCase):
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 @@ class TestStockInventory(TransactionCase):
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 @@ class TestStockInventory(TransactionCase):
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 @@ class TestStockInventory(TransactionCase):
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 @@ class TestStockInventory(TransactionCase):
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 @@ class TestStockInventory(TransactionCase):
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 @@ class TestStockInventory(TransactionCase):
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 @@ class TestStockInventory(TransactionCase):
"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(

View File

@@ -20,6 +20,13 @@
attrs="{'invisible':['|',('state', 'in', ['draft', 'done']), ('count_stock_moves', '!=', 0)]}"
string="Back to Draft"
/>
<field name="action_state_to_cancel_allowed" invisible="1" />
<button
type="object"
name="action_state_to_cancel"
attrs="{'invisible':[('action_state_to_cancel_allowed', '=', False),]}"
string="Cancel"
/>
<button
type="object"
name="action_state_to_done"
@@ -70,16 +77,11 @@
</div>
<group>
<group>
<field
name="product_selection"
widget="radio"
attrs="{'readonly':[('state', 'in', ['in_progress', 'done'])]}"
/>
<field name="product_selection" widget="radio" />
<field
name="location_ids"
string="Locations"
widget="many2many_tags"
attrs="{'readonly':[('state', 'in', ['in_progress', 'done'])]}"
required="1"
/>
<field
@@ -99,27 +101,31 @@
<field
name="product_ids"
widget="many2many_tags"
attrs="{'readonly':[('state', 'in', ['in_progress', 'done'])], 'required': [('product_selection', 'in', ['manual', 'lot'])],'invisible': [('product_selection', 'in', ['all', 'category', 'one'])]}"
attrs="{'required': [('product_selection', 'in', ['manual', 'lot'])],'invisible': [('product_selection', 'in', ['all', 'category', 'one'])]}"
/>
<field
name="product_ids"
widget="many2many_tags"
options="{'limit': 10}"
attrs="{'readonly':[('state', 'in', ['in_progress', 'done'])], 'required': [('product_selection', '=', 'one')],'invisible': [('product_selection', '!=', 'one')]}"
attrs="{'required': [('product_selection', '=', 'one')],'invisible': [('product_selection', '!=', 'one')]}"
/>
<field
name="category_id"
attrs="{'readonly':[('state', 'in', ['in_progress', 'done'])], 'required': [('product_selection', '=', 'category')],'invisible': [('product_selection', '!=', 'category')]}"
attrs="{'required': [('product_selection', '=', 'category')],'invisible': [('product_selection', '!=', 'category')]}"
/>
<field
name="lot_ids"
widget="many2many_tags"
domain="[('product_id', 'in', product_ids)]"
attrs="{'readonly':[('state', 'in', ['in_progress', 'done'])], 'required': [('product_selection', '=', 'lot')],'invisible': [('product_selection', '!=', 'lot')]}"
attrs="{'required': [('product_selection', '=', 'lot')],'invisible': [('product_selection', '!=', 'lot')]}"
/>
</group>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
</field>
</record>
@@ -147,6 +153,18 @@
</field>
</record>
<record model="ir.ui.view" id="stock_inventory_search_view">
<field name="model">stock.inventory</field>
<field name="arch" type="xml">
<search>
<field name="name" />
<field name="location_ids" />
<field name="date" />
<field name="state" />
</search>
</field>
</record>
<record id="action_view_inventory_group_form" model="ir.actions.act_window">
<field name="name">Inventory Adjustment Group</field>
<field name="res_model">stock.inventory</field>