diff --git a/setup/stock_account_inventory_discrepancy/odoo/addons/stock_account_inventory_discrepancy b/setup/stock_account_inventory_discrepancy/odoo/addons/stock_account_inventory_discrepancy new file mode 120000 index 000000000..b805695ab --- /dev/null +++ b/setup/stock_account_inventory_discrepancy/odoo/addons/stock_account_inventory_discrepancy @@ -0,0 +1 @@ +../../../../stock_account_inventory_discrepancy \ No newline at end of file diff --git a/setup/stock_account_inventory_discrepancy/setup.py b/setup/stock_account_inventory_discrepancy/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/stock_account_inventory_discrepancy/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_account_inventory_discrepancy/README.rst b/stock_account_inventory_discrepancy/README.rst new file mode 100644 index 000000000..b7420fc79 --- /dev/null +++ b/stock_account_inventory_discrepancy/README.rst @@ -0,0 +1,85 @@ +=================================== +Stock Account Inventory Discrepancy +=================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/12.0/stock_account_inventory_discrepancy + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-12-0/stock-logistics-warehouse-12-0-stock_account_inventory_discrepancy + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/153/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Extends `stock_inventory_discrepancy` module to also consider cost +discrepancies. In other words, this module adds the capability to show the +cost discrepancy of every line in an inventory adjustment and to block the +inventory adjustment validation (setting it as 'Pending to Approve') when +the discrepancy is greater than an user defined amount threshold. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +#. Go to "Inventory > Warehouse Management" > Warehouses" or to "Inventory > + Warehouse Management" > Locations". +#. Modify the "Maximum Discrepancy Amount Threshold" either in a Warehouse or + in a location. If set to 0.0 in both the threshold is disabled. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* Lois Rilo + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_account_inventory_discrepancy/__init__.py b/stock_account_inventory_discrepancy/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/stock_account_inventory_discrepancy/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_account_inventory_discrepancy/__manifest__.py b/stock_account_inventory_discrepancy/__manifest__.py new file mode 100644 index 000000000..664fe09e0 --- /dev/null +++ b/stock_account_inventory_discrepancy/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Stock Account Inventory Discrepancy", + "summary": "Adds the capability to show the value discrepancy of every " + "line in an inventory and to block the inventory validation " + "when the discrepancy is over a user defined threshold.", + "version": "13.0.1.0.0", + "author": "ForgeFlow, " "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "category": "Warehouse", + "depends": ["stock_inventory_discrepancy"], + "data": [ + "views/stock_inventory_view.xml", + "views/stock_warehouse_view.xml", + "views/stock_location_view.xml", + ], + "license": "AGPL-3", + "installable": True, + "application": False, +} diff --git a/stock_account_inventory_discrepancy/models/__init__.py b/stock_account_inventory_discrepancy/models/__init__.py new file mode 100644 index 000000000..3e1429b38 --- /dev/null +++ b/stock_account_inventory_discrepancy/models/__init__.py @@ -0,0 +1,4 @@ +from . import stock_inventory_line +from . import stock_warehouse +from . import stock_location +from . import stock_quant diff --git a/stock_account_inventory_discrepancy/models/stock_inventory_line.py b/stock_account_inventory_discrepancy/models/stock_inventory_line.py new file mode 100644 index 000000000..0674d273e --- /dev/null +++ b/stock_account_inventory_discrepancy/models/stock_inventory_line.py @@ -0,0 +1,55 @@ +# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class StockInventoryLine(models.Model): + _inherit = "stock.inventory.line" + + discrepancy_amount = fields.Monetary( + string="Amount Discrepancy", + compute="_compute_discrepancy_amount", + currency_field="company_currency_id", + help="The difference between the actual qty counted and the " + "theoretical quantity on hand expressed in the cost amount.", + digits="Product Unit of Measure", + default=0, + ) + discrepancy_amount_threshold = fields.Monetary( + string="Amount Threshold", + currency_field="company_currency_id", + help="Maximum Discrepancy Amount Threshold", + compute="_compute_discrepancy_amount_threshold", + ) + company_currency_id = fields.Many2one( + string="Company Currency", + comodel_name="res.currency", + related="inventory_id.company_id.currency_id", + readonly=False, + ) + + @api.depends("theoretical_qty", "product_qty") + def _compute_discrepancy_amount(self): + for line in self: + discrepancy_qty = line.product_qty - line.theoretical_qty + cost = line.product_id.standard_price + line.discrepancy_amount = discrepancy_qty * cost + + def _compute_discrepancy_amount_threshold(self): + for line in self: + whs = line.location_id.get_warehouse() + if line.location_id.discrepancy_amount_threshold > 0.0: + line.discrepancy_amount_threshold = ( + line.location_id.discrepancy_amount_threshold + ) + elif whs.discrepancy_amount_threshold > 0.0: + line.discrepancy_amount_threshold = whs.discrepancy_amount_threshold + else: + line.discrepancy_amount_threshold = False + + def _has_over_discrepancy(self): + res = super()._has_over_discrepancy() + return ( + res or abs(self.discrepancy_amount) > self.discrepancy_amount_threshold > 0 + ) diff --git a/stock_account_inventory_discrepancy/models/stock_location.py b/stock_account_inventory_discrepancy/models/stock_location.py new file mode 100644 index 000000000..cee529ea4 --- /dev/null +++ b/stock_account_inventory_discrepancy/models/stock_location.py @@ -0,0 +1,19 @@ +# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class StockLocation(models.Model): + _inherit = "stock.location" + + discrepancy_amount_threshold = fields.Monetary( + string="Maximum Discrepancy Amount Threshold", + currency_field="discrepancy_amount_threshold_currency_id", + help="Maximum Discrepancy Amount allowed for any product when doing " + "an Inventory Adjustment. Thresholds defined in Locations have " + "preference over Warehouse's ones.", + ) + discrepancy_amount_threshold_currency_id = fields.Many2one( + comodel_name="res.currency", related="company_id.currency_id", readonly=True, + ) diff --git a/stock_account_inventory_discrepancy/models/stock_quant.py b/stock_account_inventory_discrepancy/models/stock_quant.py new file mode 100644 index 000000000..186727384 --- /dev/null +++ b/stock_account_inventory_discrepancy/models/stock_quant.py @@ -0,0 +1,40 @@ +# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import _, models +from odoo.exceptions import UserError + + +class Quant(models.Model): + _inherit = "stock.quant" + + def _has_over_discrepancy(self, qty=None): + self.ensure_one() + whs = self.location_id.get_warehouse() + if self.location_id.discrepancy_amount_threshold > 0.0: + discrepancy_amount_threshold = self.location_id.discrepancy_amount_threshold + elif whs.discrepancy_amount_threshold > 0.0: + discrepancy_amount_threshold = whs.discrepancy_amount_threshold + else: + return False + + discrepancy_qty = qty or self.quantity + cost = self.product_id.standard_price + discrepancy_amount = discrepancy_qty * cost + + return abs(discrepancy_amount) > discrepancy_amount_threshold > 0 + + def _set_inventory_quantity(self): + for rec in self: + variance = self.inventory_quantity - rec.quantity + if rec._has_over_discrepancy(qty=variance) and not self.user_has_groups( + "stock_inventory_discrepancy.group_stock_inventory_validation_always" + ): + raise UserError( + _( + "The Qty Update is over the Discrepancy Threshold.\n " + "Please, contact a user with rights to perform " + "this action." + ) + ) + return super(Quant, self)._set_inventory_quantity() diff --git a/stock_account_inventory_discrepancy/models/stock_warehouse.py b/stock_account_inventory_discrepancy/models/stock_warehouse.py new file mode 100644 index 000000000..e756d8174 --- /dev/null +++ b/stock_account_inventory_discrepancy/models/stock_warehouse.py @@ -0,0 +1,19 @@ +# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class StockWarehouse(models.Model): + _inherit = "stock.warehouse" + + discrepancy_amount_threshold = fields.Monetary( + string="Maximum Discrepancy Amount Threshold", + currency_field="discrepancy_amount_threshold_currency_id", + help="Maximum Discrepancy Amount allowed for any product when doing " + "an Inventory Adjustment. Threshold defined in involved Location " + "has preference.", + ) + discrepancy_amount_threshold_currency_id = fields.Many2one( + comodel_name="res.currency", related="company_id.currency_id", readonly=True, + ) diff --git a/stock_account_inventory_discrepancy/readme/CONFIGURE.rst b/stock_account_inventory_discrepancy/readme/CONFIGURE.rst new file mode 100644 index 000000000..9e2200468 --- /dev/null +++ b/stock_account_inventory_discrepancy/readme/CONFIGURE.rst @@ -0,0 +1,4 @@ +#. Go to "Inventory > Warehouse Management" > Warehouses" or to "Inventory > + Warehouse Management" > Locations". +#. Modify the "Maximum Discrepancy Amount Threshold" either in a Warehouse or + in a location. If set to 0.0 in both the threshold is disabled. diff --git a/stock_account_inventory_discrepancy/readme/CONTRIBUTORS.rst b/stock_account_inventory_discrepancy/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..77dfbe89e --- /dev/null +++ b/stock_account_inventory_discrepancy/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Lois Rilo diff --git a/stock_account_inventory_discrepancy/readme/DESCRIPTION.rst b/stock_account_inventory_discrepancy/readme/DESCRIPTION.rst new file mode 100644 index 000000000..87a98a671 --- /dev/null +++ b/stock_account_inventory_discrepancy/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +Extends `stock_inventory_discrepancy` module to also consider cost +discrepancies. In other words, this module adds the capability to show the +cost discrepancy of every line in an inventory adjustment and to block the +inventory adjustment validation (setting it as 'Pending to Approve') when +the discrepancy is greater than an user defined amount threshold. diff --git a/stock_account_inventory_discrepancy/static/description/icon.png b/stock_account_inventory_discrepancy/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/stock_account_inventory_discrepancy/static/description/icon.png differ diff --git a/stock_account_inventory_discrepancy/static/description/index.html b/stock_account_inventory_discrepancy/static/description/index.html new file mode 100644 index 000000000..e27837f16 --- /dev/null +++ b/stock_account_inventory_discrepancy/static/description/index.html @@ -0,0 +1,433 @@ + + + + + + +Stock Account Inventory Discrepancy + + + +
+

Stock Account Inventory Discrepancy

+ + +

Beta License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

+

Extends stock_inventory_discrepancy module to also consider cost +discrepancies. In other words, this module adds the capability to show the +cost discrepancy of every line in an inventory adjustment and to block the +inventory adjustment validation (setting it as ‘Pending to Approve’) when +the discrepancy is greater than an user defined amount threshold.

+

Table of contents

+ +
+

Configuration

+
    +
  1. Go to “Inventory > Warehouse Management” > Warehouses” or to “Inventory > +Warehouse Management” > Locations”.
  2. +
  3. Modify the “Maximum Discrepancy Amount Threshold” either in a Warehouse or +in a location. If set to 0.0 in both the threshold is disabled.
  4. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • ForgeFlow
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/stock-logistics-warehouse project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/stock_account_inventory_discrepancy/tests/__init__.py b/stock_account_inventory_discrepancy/tests/__init__.py new file mode 100644 index 000000000..41e817a8a --- /dev/null +++ b/stock_account_inventory_discrepancy/tests/__init__.py @@ -0,0 +1 @@ +from . import test_inventory_discrepancy diff --git a/stock_account_inventory_discrepancy/tests/test_inventory_discrepancy.py b/stock_account_inventory_discrepancy/tests/test_inventory_discrepancy.py new file mode 100644 index 000000000..a393af56c --- /dev/null +++ b/stock_account_inventory_discrepancy/tests/test_inventory_discrepancy.py @@ -0,0 +1,200 @@ +# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo.exceptions import except_orm +from odoo.tests.common import TransactionCase + + +class TestInventoryDiscrepancy(TransactionCase): + def setUp(self, *args, **kwargs): + super().setUp(*args, **kwargs) + self.obj_wh = self.env["stock.warehouse"] + self.obj_location = self.env["stock.location"] + self.obj_inventory = self.env["stock.inventory"] + self.obj_product = self.env["product.product"] + self.obj_warehouse = self.env["stock.warehouse"] + self.obj_upd_qty_wizard = self.env["stock.change.product.qty"] + + self.product1 = self.obj_product.create( + { + "name": "Test Product 1", + "type": "product", + "default_code": "PROD1", + "standard_price": 110.0, + } + ) + self.product2 = self.obj_product.create( + { + "name": "Test Product 2", + "type": "product", + "default_code": "PROD2", + "standard_price": 150.0, + } + ) + self.test_loc = self.obj_location.create( + { + "name": "Test Location", + "usage": "internal", + "discrepancy_amount_threshold": 100, + } + ) + self.test_wh = self.obj_warehouse.create( + {"name": "Test WH", "code": "T", "discrepancy_amount_threshold": 300} + ) + self.obj_location._parent_store_compute() + + # Create Stock manager able to force validation on inventories. + group_stock_man = self.env.ref("stock.group_stock_manager") + group_inventory_all = self.env.ref( + "stock_inventory_discrepancy." "group_stock_inventory_validation_always" + ) + self.manager = self.env["res.users"].create( + { + "name": "Test Manager", + "login": "manager", + "email": "test.manager@example.com", + "groups_id": [(6, 0, [group_stock_man.id, group_inventory_all.id])], + } + ) + group_stock_user = self.env.ref("stock.group_stock_user") + self.user = self.env["res.users"].create( + { + "name": "Test User", + "login": "user", + "email": "test.user@example.com", + "groups_id": [(6, 0, [group_stock_user.id])], + } + ) + + starting_inv = self.obj_inventory.create( + { + "name": "Starting inventory", + "line_ids": [ + ( + 0, + 0, + { + "product_id": self.product1.id, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + "product_qty": 2.0, + "location_id": self.test_loc.id, + }, + ), + ( + 0, + 0, + { + "product_id": self.product2.id, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + "product_qty": 4.0, + "location_id": self.test_loc.id, + }, + ), + ], + } + ) + starting_inv.action_force_done() + + def test_compute_discrepancy(self): + """Tests if the amount discrepancy is correctly computed. + """ + inventory = self.obj_inventory.create( + { + "name": "Test Discrepancy Computation", + "location_ids": [(6, 0, self.test_loc.ids)], + "line_ids": [ + ( + 0, + 0, + { + "product_id": self.product1.id, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + "product_qty": 3.0, + "location_id": self.test_loc.id, + }, + ), + ( + 0, + 0, + { + "product_id": self.product2.id, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + "product_qty": 3.0, + "location_id": self.test_loc.id, + }, + ), + ], + } + ) + self.assertEqual(inventory.line_ids[0].discrepancy_amount, 110.0) + self.assertEqual(inventory.line_ids[1].discrepancy_amount, -150.0) + + def test_amount_discrepancy_validation(self): + """Tests the workflow with amount threshold.""" + inventory = self.obj_inventory.create( + { + "name": "Test Forcing Validation Method", + "location_ids": [(6, 0, self.test_loc.ids)], + "line_ids": [ + ( + 0, + 0, + { + "product_id": self.product1.id, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + "product_qty": 3.0, + "location_id": self.test_loc.id, + }, + ), + ], + } + ) + self.assertEqual(inventory.state, "draft") + inventory.action_start() + self.assertEqual(inventory.line_ids.discrepancy_amount_threshold, 100) + self.assertTrue(inventory.line_ids[0].has_over_discrepancy) + inventory.with_user(self.user).with_context( + {"normal_view": True} + ).action_validate() + self.assertEqual(inventory.over_discrepancy_line_count, 1) + self.assertEqual(inventory.state, "pending") + + def test_warehouse_amount_threshold(self): + """Tests the behaviour if the threshold is set on the WH.""" + inventory = self.obj_inventory.create( + { + "name": "Test Threshold Defined in WH", + "location_ids": [(6, 0, self.test_wh.view_location_id.ids)], + "line_ids": [ + ( + 0, + 0, + { + "product_id": self.product1.id, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + "product_qty": 3.0, + "location_id": self.test_wh.lot_stock_id.id, + }, + ), + ], + } + ) + self.assertEqual(inventory.line_ids.discrepancy_amount_threshold, 300) + + def test_update_qty_user_error_amount(self): + """Test if a user error raises when a stock user tries to update the + qty for a product and the correction is a discrepancy over the + threshold.""" + upd_qty = self.obj_upd_qty_wizard.with_user(self.user).create( + { + "product_id": self.product1.id, + "product_tmpl_id": self.product1.product_tmpl_id.id, + "new_quantity": 10.0, + } + ) + # Since v13, stock users are not allowed to update product qty + # (AccessError, standard) but if access are modified to allow them, + # they will not be able to surpass the threshold (UserError + # added in this module). + with self.assertRaises(except_orm): + upd_qty.change_product_qty() diff --git a/stock_account_inventory_discrepancy/views/stock_inventory_view.xml b/stock_account_inventory_discrepancy/views/stock_inventory_view.xml new file mode 100644 index 000000000..fde35a414 --- /dev/null +++ b/stock_account_inventory_discrepancy/views/stock_inventory_view.xml @@ -0,0 +1,19 @@ + + + + + stock.inventory.line.tree2 - stock_account_inventory_discrepancy + stock.inventory.line + + + + + + + + + + diff --git a/stock_account_inventory_discrepancy/views/stock_location_view.xml b/stock_account_inventory_discrepancy/views/stock_location_view.xml new file mode 100644 index 000000000..5ab008548 --- /dev/null +++ b/stock_account_inventory_discrepancy/views/stock_location_view.xml @@ -0,0 +1,22 @@ + + + + + Location form - stock_account_inventory_discrepancy + stock.location + + + + + + + diff --git a/stock_account_inventory_discrepancy/views/stock_warehouse_view.xml b/stock_account_inventory_discrepancy/views/stock_warehouse_view.xml new file mode 100644 index 000000000..252bd6cf6 --- /dev/null +++ b/stock_account_inventory_discrepancy/views/stock_warehouse_view.xml @@ -0,0 +1,22 @@ + + + + + stock.warehouse - stock_account_inventory_discrepancy + stock.warehouse + + + + + + +