-
-
Notifications
You must be signed in to change notification settings - Fork 723
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b94a8a1
commit 257254b
Showing
19 changed files
with
440 additions
and
0 deletions.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
setup/stock_inventory_security/odoo/addons/stock_inventory_security
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../../stock_inventory_security |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import setuptools | ||
|
||
setuptools.setup( | ||
setup_requires=['setuptools-odoo'], | ||
odoo_addon=True, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# To be generated by bot |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Copyright 2024 Camptocamp SA (https://www.camptocamp.com). | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
|
||
{ | ||
"name": "Stock Quant Inventory Security", | ||
"summary": "Dedicated security group to apply inventory adjustments", | ||
"version": "16.0.1.0.0", | ||
"author": "Camptocamp, Odoo Community Association (OCA)", | ||
"maintainers": ["ivantodorovich"], | ||
"website": "https://github.com/OCA/stock-logistics-warehouse", | ||
"license": "AGPL-3", | ||
"category": "Inventory", | ||
"depends": ["stock"], | ||
"data": [ | ||
"security/security.xml", | ||
"security/ir.model.access.csv", | ||
"views/product.xml", | ||
"views/stock_quant.xml", | ||
], | ||
"demo": [ | ||
"demo/demo.xml", | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<!-- | ||
Copyright 2024 Camptocamp SA (https://www.camptocamp.com). | ||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
--> | ||
<odoo> | ||
|
||
<record id="base.user_demo" model="res.users"> | ||
<field | ||
name="groups_id" | ||
eval="[Command.link(ref('group_inventory_adjustment'))]" | ||
/> | ||
</record> | ||
|
||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from . import product_product | ||
from . import stock_location | ||
from . import stock_lot | ||
from . import stock_quant |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Copyright 2024 Camptocamp SA (https://www.camptocamp.com). | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
|
||
from odoo import models | ||
|
||
|
||
class ProductProduct(models.Model): | ||
_inherit = "product.product" | ||
|
||
def user_has_groups(self, groups): | ||
# Most inventory adjustment operations are limited to users having | ||
# the Inventory Manager group. | ||
# OVERRIDE: Hijack the check to replace it with our own group. | ||
if groups == "stock.group_stock_manager" and self.env.context.get( | ||
"_stock_inventory_security" | ||
): | ||
groups = "stock_inventory_security.group_inventory_adjustment" | ||
return super().user_has_groups(groups) | ||
|
||
def action_open_quants(self): | ||
if self.user_has_groups("stock_inventory_security.group_inventory_adjustment"): | ||
self = self.with_context(_stock_inventory_security=True) | ||
return super().action_open_quants() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Copyright 2024 Camptocamp SA (https://www.camptocamp.com). | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
|
||
from odoo import models | ||
|
||
|
||
class StockLocation(models.Model): | ||
_inherit = "stock.location" | ||
|
||
def write(self, vals): | ||
# OVERRIDE: Allow the inventory user to set the last inventory date. | ||
# https://github.com/odoo/odoo/blob/534220ee/addons/stock/models/stock_quant.py#L775 | ||
if ( | ||
self.env.context.get("_stock_inventory_security") | ||
and len(vals) == 1 | ||
and "last_inventory_date" in vals | ||
and self.user_has_groups( | ||
"stock_inventory_security.group_inventory_adjustment" | ||
) | ||
): | ||
self = self.sudo() | ||
return super().write(vals) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Copyright 2024 Camptocamp SA (https://www.camptocamp.com). | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
|
||
from odoo import models | ||
|
||
|
||
class StockLot(models.Model): | ||
_inherit = "stock.lot" | ||
|
||
def user_has_groups(self, groups): | ||
# Most inventory adjustment operations are limited to users having | ||
# the Inventory Manager group. | ||
# OVERRIDE: Hijack the check to replace it with our own group.) | ||
if groups == "stock.group_stock_manager" and self.env.context.get( | ||
"_stock_inventory_security" | ||
): | ||
groups = "stock_inventory_security.group_inventory_adjustment" | ||
return super().user_has_groups(groups) | ||
|
||
def action_lot_open_quants(self): | ||
# OVERRIDE: Add the inventory_mode context | ||
if self.user_has_groups("stock_inventory_security.group_inventory_adjustment"): | ||
self = self.with_context(_stock_inventory_security=True) | ||
return super().action_lot_open_quants() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Copyright 2024 Camptocamp SA (https://www.camptocamp.com). | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
|
||
from odoo import fields, models | ||
|
||
|
||
class StockQuant(models.Model): | ||
_inherit = "stock.quant" | ||
|
||
inventory_quantity_auto_apply = fields.Float( | ||
# Change stock.group_stock_manager to our own group. | ||
groups="stock_inventory_security.group_inventory_adjustment", | ||
) | ||
|
||
def user_has_groups(self, groups): | ||
# Most inventory adjustment operations are limited to users having | ||
# the Inventory Manager group. | ||
# OVERRIDE: Hijack the check to replace it with our own group. | ||
if groups == "stock.group_stock_manager" and self.env.context.get( | ||
"_stock_inventory_security" | ||
): | ||
groups = "stock_inventory_security.group_inventory_adjustment" | ||
return super().user_has_groups(groups) | ||
|
||
def _get_quants_action(self, domain=None, extend=False): | ||
# OVERRIDE: Show the editable quants view for users having the Inventory | ||
# Adjustments group. | ||
# The original method would only do it for Stock Managers. | ||
if self.user_has_groups("stock_inventory_security.group_inventory_adjustment"): | ||
self = self.with_context(_stock_inventory_security=True) | ||
return super()._get_quants_action(domain=domain, extend=extend) | ||
|
||
def action_view_inventory(self): | ||
# OVERRIDE: Disable the "My count" filter for users having the Inventory | ||
# Adjustments group. | ||
if self.user_has_groups("stock_inventory_security.group_inventory_adjustment"): | ||
self = self.with_context(_stock_inventory_security=True) | ||
return super().action_view_inventory() | ||
|
||
def _apply_inventory(self): | ||
if self.user_has_groups("stock_inventory_security.group_inventory_adjustment"): | ||
self = self.with_context(_stock_inventory_security=True) | ||
return super()._apply_inventory() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* Iván Todorovich <[email protected]> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
In standard, the inventory adjustments can only be applied by **Inventory / Manager** users. | ||
|
||
This module introduces a new security group named **Stock: Inventory Adjustments**, which | ||
grants regular stock users the ability to apply inventory adjustments. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink | ||
access_stock_inventory_adjustment_name,stock.inventory.adjustment.name,stock.model_stock_inventory_adjustment_name,group_inventory_adjustment,1,1,1,0 | ||
access_stock_request_count,stock.request.count,stock.model_stock_request_count,group_inventory_adjustment,1,1,1,0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<odoo noupdate="1"> | ||
|
||
<record id="group_inventory_adjustment" model="res.groups"> | ||
<field name="name">Stock: Inventory Adjustments</field> | ||
<field | ||
name="implied_ids" | ||
eval="[Command.link(ref('stock.group_stock_user'))]" | ||
/> | ||
</record> | ||
|
||
<record id="stock.group_stock_manager" model="res.groups"> | ||
<field | ||
name="implied_ids" | ||
eval="[Command.link(ref('group_inventory_adjustment'))]" | ||
/> | ||
</record> | ||
|
||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import test_stock_inventory_security |
156 changes: 156 additions & 0 deletions
156
stock_inventory_security/tests/test_stock_inventory_security.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
# Copyright 2024 Camptocamp SA (https://www.camptocamp.com). | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
|
||
from odoo.exceptions import UserError | ||
from odoo.tests import Form, TransactionCase, new_test_user, users | ||
|
||
from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT | ||
|
||
|
||
class TestStockInventorySecurity(TransactionCase): | ||
@classmethod | ||
def setUpClass(cls): | ||
super().setUpClass() | ||
cls.env = cls.env(context=dict(cls.env.context, **DISABLED_MAIL_CONTEXT)) | ||
# Lazy tests compatibility with `stock_inventory_discrepancy` | ||
cls.env = cls.env(context=dict(cls.env.context, skip_exceeded_discrepancy=True)) | ||
# Create test records | ||
cls.inventory_user = new_test_user( | ||
cls.env, | ||
login="inventory", | ||
groups="stock_inventory_security.group_inventory_adjustment", | ||
) | ||
cls.stock_user = new_test_user( | ||
cls.env, | ||
login="stock", | ||
groups="stock.group_stock_user", | ||
) | ||
cls.stock_location = cls.env.ref("stock.stock_location_stock") | ||
cls.product = cls.env["product.product"].create( | ||
{ | ||
"name": "Test product", | ||
"type": "product", | ||
} | ||
) | ||
cls.product_lot = cls.env["stock.lot"].create( | ||
{ | ||
"product_id": cls.product.id, | ||
"company_id": cls.env.company.id, | ||
} | ||
) | ||
cls.product_quants = ( | ||
cls.env["stock.quant"] | ||
.with_context(inventory_mode=True) | ||
.create( | ||
{ | ||
"product_id": cls.product.id, | ||
"inventory_quantity": 4, | ||
"lot_id": cls.product_lot.id, | ||
"location_id": cls.stock_location.id, | ||
} | ||
) | ||
) | ||
cls.product_quants.action_apply_inventory() | ||
|
||
@users("inventory", "admin") | ||
def test_inventory_user_product_action_open_quants(self): | ||
"""Test that the inventory user gets into inventory mode from products""" | ||
res = self.product.with_user(self.env.user).action_open_quants() | ||
self.assertFalse(res["context"].get("search_default_my_count")) | ||
|
||
@users("stock") | ||
def test_stock_user_product_action_open_quants(self): | ||
"""Test that the stock user does not get into inventory mode from products""" | ||
res = self.product.with_user(self.env.user).action_open_quants() | ||
self.assertTrue(res["context"].get("search_default_my_count")) | ||
|
||
@users("inventory", "admin") | ||
def test_inventory_user_quant_action(self): | ||
"""Test that the inventory user gets into inventory mode from quants""" | ||
res = self.product_quants.with_user(self.env.user).action_view_inventory() | ||
self.assertFalse(res["context"].get("search_default_my_count")) | ||
|
||
@users("stock") | ||
def test_stock_user_quant_action(self): | ||
"""Test that the stock user does not get into inventory mode from quants""" | ||
res = self.product_quants.with_user(self.env.user).action_view_inventory() | ||
self.assertTrue(res["context"].get("search_default_my_count")) | ||
|
||
@users("inventory", "admin") | ||
def test_inventory_user_lot_action(self): | ||
"""Test that the inventory user gets into inventory mode from lots""" | ||
res = self.product_lot.with_user(self.env.user).action_lot_open_quants() | ||
self.assertEqual( | ||
res["view_id"], self.env.ref("stock.view_stock_quant_tree_editable").id | ||
) | ||
self.assertTrue(res["context"].get("inventory_mode")) | ||
|
||
@users("stock") | ||
def test_stock_user_lot_action(self): | ||
"""Test that the stock user does not get into inventory mode from lots""" | ||
res = self.product_lot.with_user(self.stock_user).action_lot_open_quants() | ||
self.assertEqual(res["view_id"], self.env.ref("stock.view_stock_quant_tree").id) | ||
self.assertFalse(res["context"].get("inventory_mode")) | ||
|
||
@users("inventory", "admin") | ||
def test_inventory_user_apply_inventory(self): | ||
"""Test that the inventory user can apply inventory""" | ||
quant = ( | ||
self.env["stock.quant"] | ||
.with_context(inventory_mode=True) | ||
.create( | ||
{ | ||
"product_id": self.product.id, | ||
"inventory_quantity": 10, | ||
"lot_id": self.product_lot.id, | ||
"location_id": self.stock_location.id, | ||
} | ||
) | ||
) | ||
quant.action_apply_inventory() | ||
self.assertEqual(self.product.qty_available, 10) | ||
|
||
@users("stock") | ||
def test_stock_user_apply_inventory(self): | ||
"""Test that the stock user cannot apply inventory""" | ||
with self.assertRaisesRegex( | ||
UserError, "Only a stock manager can validate an inventory adjustment." | ||
): | ||
quant = self.env["stock.quant"].create( | ||
{ | ||
"product_id": self.product.id, | ||
"inventory_quantity": 10, | ||
"lot_id": self.product_lot.id, | ||
"location_id": self.stock_location.id, | ||
} | ||
) | ||
quant.action_apply_inventory() | ||
|
||
@users("inventory", "admin") | ||
def test_inventory_user_apply_inventory_reason(self): | ||
"""Test that the inventory user can apply inventory with a reason""" | ||
quant = ( | ||
self.env["stock.quant"] | ||
.with_context(inventory_mode=True) | ||
.create( | ||
{ | ||
"product_id": self.product.id, | ||
"lot_id": self.product_lot.id, | ||
"location_id": self.stock_location.id, | ||
"inventory_quantity": 10, | ||
} | ||
) | ||
) | ||
form_wizard = Form( | ||
self.env["stock.inventory.adjustment.name"].with_context( | ||
default_quant_ids=quant.ids | ||
) | ||
) | ||
form_wizard.inventory_adjustment_name = "Inventory Adjustment - Test" | ||
form_wizard.save().action_apply() | ||
self.assertTrue( | ||
self.env["stock.move"].search( | ||
[("reference", "=", "Inventory Adjustment - Test")], limit=1 | ||
) | ||
) | ||
self.assertEqual(self.product.qty_available, 10) |
Oops, something went wrong.