diff --git a/setup/stock_quant_safe_inventory/odoo/addons/stock_quant_safe_inventory b/setup/stock_quant_safe_inventory/odoo/addons/stock_quant_safe_inventory new file mode 120000 index 000000000..57801c440 --- /dev/null +++ b/setup/stock_quant_safe_inventory/odoo/addons/stock_quant_safe_inventory @@ -0,0 +1 @@ +../../../../stock_quant_safe_inventory \ No newline at end of file diff --git a/setup/stock_quant_safe_inventory/setup.py b/setup/stock_quant_safe_inventory/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/stock_quant_safe_inventory/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_quant_safe_inventory/README.rst b/stock_quant_safe_inventory/README.rst new file mode 100644 index 000000000..881426fc2 --- /dev/null +++ b/stock_quant_safe_inventory/README.rst @@ -0,0 +1,103 @@ +========================== +Stock Quant Safe Inventory +========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:8d5ca98684eb4061dab8a6fd933f66881a81d33a3d885ebfc8b0d1e87dd9849d + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/16.0/stock_quant_safe_inventory + :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-16-0/stock-logistics-warehouse-16-0-stock_quant_safe_inventory + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-warehouse&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module prevents the user from updating the quantity on a quant if +some quantity has been put as done on a move line not yet validated for +the same product, location, lot and package. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +In odoo, you can update the quantity on a quant by filling the counted +quantity on the quant form view and clicking on the "Apply inventory" +button at the end of the line. Unfortunately, nothing prevents you from +doing this for a quant and location in front of which you are standing +to perform an inventory while some quantity have already been picked +from the location by another user but not yet validated. This can lead +to stock discrepancies since the quantity you are updating is not the +actual quantity in the location for the system and when the picking will +be validated, the system will decrease the quantity you just updated by +the quantity that was picked by the other user. + +This module prevents this by preventing the user from updating the +quantity on a quant if some quantity has been put as done on a move line +not yet validated for the same product, location, lot and package. + +Usage +===== + +To prevent inventory of quants being processed you must check the +``Stock quant no inventory if being picked`` parameter into the stock +settings panel. (stock -> configuration -> settings) + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* ACSONE SA/NV + +Contributors +------------ + +- Laurent Mignon laurent.mignon@acsone.eu (https://www.acsone.eu) + +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_quant_safe_inventory/__init__.py b/stock_quant_safe_inventory/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/stock_quant_safe_inventory/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_quant_safe_inventory/__manifest__.py b/stock_quant_safe_inventory/__manifest__.py new file mode 100644 index 000000000..7fe4fdcd3 --- /dev/null +++ b/stock_quant_safe_inventory/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Stock Quant Safe Inventory", + "summary": """ + Prevents the quantity on the quant from being updated if quantities have + already been picked but not validated in pickings in progress.""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "depends": [ + "stock", + ], + "data": [ + "views/res_config_settings.xml", + ], + "demo": [], + "installable": True, +} diff --git a/stock_quant_safe_inventory/models/__init__.py b/stock_quant_safe_inventory/models/__init__.py new file mode 100644 index 000000000..e55dfd12a --- /dev/null +++ b/stock_quant_safe_inventory/models/__init__.py @@ -0,0 +1,3 @@ +from . import res_company +from . import res_config_settings +from . import stock_quant diff --git a/stock_quant_safe_inventory/models/res_company.py b/stock_quant_safe_inventory/models/res_company.py new file mode 100644 index 000000000..9ccf95029 --- /dev/null +++ b/stock_quant_safe_inventory/models/res_company.py @@ -0,0 +1,16 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + stock_quant_no_inventory_if_being_picked = fields.Boolean( + string="Stock quant no inventory if being picked", + help="If checked, the system will prevent inventory of stock quants if " + "some quantities are currently being picked for the same product, " + "location, lot and package.", + default=False, + ) diff --git a/stock_quant_safe_inventory/models/res_config_settings.py b/stock_quant_safe_inventory/models/res_config_settings.py new file mode 100644 index 000000000..44310a941 --- /dev/null +++ b/stock_quant_safe_inventory/models/res_config_settings.py @@ -0,0 +1,12 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + stock_quant_no_inventory_if_being_picked = fields.Boolean( + related="company_id.stock_quant_no_inventory_if_being_picked", readonly=False + ) diff --git a/stock_quant_safe_inventory/models/stock_quant.py b/stock_quant_safe_inventory/models/stock_quant.py new file mode 100644 index 000000000..06ac3d747 --- /dev/null +++ b/stock_quant_safe_inventory/models/stock_quant.py @@ -0,0 +1,89 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, models +from odoo.exceptions import UserError +from odoo.tools import groupby + + +class StockQuant(models.Model): + _inherit = "stock.quant" + + @api.model + def _quant_move_common_keys(self): + """Return the list of fields that are used to select quant from move lines.""" + return [ + "location_id", + "product_id", + "lot_id", + "package_id", + "owner_id", + "company_id", + ] + + def _get_current_move_lines(self): + """Return a dictionary of move lines that are currently in progress + by quant. + + This method returns a dictionary with the key being the quant id and + the value being a list of move lines that are currently in progress + for that quant. + + A move line is considered to be in progress if it is not done and + its quantity done is positive for the same product, location, lot + and package as the quant. + """ + quants = self.filtered("reserved_quantity") + quant_selection_keys = self._quant_move_common_keys() + move_lines = self.env["stock.move.line"].search( + [ + ("state", "not in", ("done", "cancel")), + ("location_id", "in", quants.mapped("location_id").ids), + ("qty_done", ">", 0.0), + ] + ) + move_lines_by_location_by_product = dict( + groupby( + move_lines, + lambda ml: tuple([ml[quant_key] for quant_key in quant_selection_keys]), + ) + ) + ret = {} + for quant in quants: + key = tuple([quant[quant_key] for quant_key in quant_selection_keys]) + move_lines = move_lines_by_location_by_product.get(key, []) + if move_lines: + ret[quant.id] = move_lines + return ret + + def _check_update_quantity_allowed(self, raise_exception=True): + if not self.env.company.stock_quant_no_inventory_if_being_picked: + return + current_move_lines = self._get_current_move_lines() + if current_move_lines: + if raise_exception: + details = [] + for quant in self: + move_lines = current_move_lines.get(quant.id, []) + if move_lines: + details.extend( + f"{move_line.qty_done} {move_line.product_id.name} " + f"-> {move_line.location_id.name}" + for move_line in move_lines + ) + raise UserError( + _( + "You cannot update the quantity of a quant that is " + "currently being picked.\n %(details)s", + details="\n".join(details), + ) + ) + + def write(self, vals): + if "inventory_quantity" in vals: + self._check_update_quantity_allowed() + return super().write(vals) + + def _apply_inventory(self): + self.filtered("inventory_diff_quantity")._check_update_quantity_allowed() + return super()._apply_inventory() diff --git a/stock_quant_safe_inventory/readme/CONTEXT.md b/stock_quant_safe_inventory/readme/CONTEXT.md new file mode 100644 index 000000000..b38c47bd7 --- /dev/null +++ b/stock_quant_safe_inventory/readme/CONTEXT.md @@ -0,0 +1,13 @@ +In odoo, you can update the quantity on a quant by filling the +counted quantity on the quant form view and clicking on the "Apply inventory" +button at the end of the line. Unfortunately, nothing prevents you from doing +this for a quant and location in front of which you are standing to perform +an inventory while some quantity have already been picked from the location by +another user but not yet validated. This can lead to stock discrepancies since +the quantity you are updating is not the actual quantity in the location for the +system and when the picking will be validated, the system will decrease the +quantity you just updated by the quantity that was picked by the other user. + +This module prevents this by preventing the user from updating the quantity on +a quant if some quantity has been put as done on a move line not yet validated +for the same product, location, lot and package. diff --git a/stock_quant_safe_inventory/readme/CONTRIBUTORS.md b/stock_quant_safe_inventory/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..a15e3ef24 --- /dev/null +++ b/stock_quant_safe_inventory/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Laurent Mignon (https://www.acsone.eu) diff --git a/stock_quant_safe_inventory/readme/DESCRIPTION.md b/stock_quant_safe_inventory/readme/DESCRIPTION.md new file mode 100644 index 000000000..725b083e2 --- /dev/null +++ b/stock_quant_safe_inventory/readme/DESCRIPTION.md @@ -0,0 +1,4 @@ +This module prevents the user from updating the quantity on a quant if some +quantity has been put as done on a move line not yet validated for the same +product, location, lot and package. + diff --git a/stock_quant_safe_inventory/readme/USAGE.md b/stock_quant_safe_inventory/readme/USAGE.md new file mode 100644 index 000000000..15bc862cd --- /dev/null +++ b/stock_quant_safe_inventory/readme/USAGE.md @@ -0,0 +1,3 @@ +To prevent inventory of quants being processed you must check the +`Stock quant no inventory if being picked` parameter into the stock +settings panel. (stock -> configuration -> settings) diff --git a/stock_quant_safe_inventory/static/description/icon.png b/stock_quant_safe_inventory/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/stock_quant_safe_inventory/static/description/icon.png differ diff --git a/stock_quant_safe_inventory/static/description/index.html b/stock_quant_safe_inventory/static/description/index.html new file mode 100644 index 000000000..dc3dc72a2 --- /dev/null +++ b/stock_quant_safe_inventory/static/description/index.html @@ -0,0 +1,447 @@ + + + + + + +Stock Quant Safe Inventory + + + +
+

Stock Quant Safe Inventory

+ + +

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

+

This module prevents the user from updating the quantity on a quant if +some quantity has been put as done on a move line not yet validated for +the same product, location, lot and package.

+

Table of contents

+ +
+

Use Cases / Context

+

In odoo, you can update the quantity on a quant by filling the counted +quantity on the quant form view and clicking on the “Apply inventory” +button at the end of the line. Unfortunately, nothing prevents you from +doing this for a quant and location in front of which you are standing +to perform an inventory while some quantity have already been picked +from the location by another user but not yet validated. This can lead +to stock discrepancies since the quantity you are updating is not the +actual quantity in the location for the system and when the picking will +be validated, the system will decrease the quantity you just updated by +the quantity that was picked by the other user.

+

This module prevents this by preventing the user from updating the +quantity on a quant if some quantity has been put as done on a move line +not yet validated for the same product, location, lot and package.

+
+
+

Usage

+

To prevent inventory of quants being processed you must check the +Stock quant no inventory if being picked parameter into the stock +settings panel. (stock -> configuration -> settings)

+
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
+
+ +
+

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_quant_safe_inventory/tests/__init__.py b/stock_quant_safe_inventory/tests/__init__.py new file mode 100644 index 000000000..8f2e66c79 --- /dev/null +++ b/stock_quant_safe_inventory/tests/__init__.py @@ -0,0 +1 @@ +from . import test_safe_inventory diff --git a/stock_quant_safe_inventory/tests/test_safe_inventory.py b/stock_quant_safe_inventory/tests/test_safe_inventory.py new file mode 100644 index 000000000..8a773309d --- /dev/null +++ b/stock_quant_safe_inventory/tests/test_safe_inventory.py @@ -0,0 +1,73 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.exceptions import UserError + +from odoo.addons.base.tests.common import BaseCommon + + +class TestSafeInventory(BaseCommon): + def setUp(self): + super().setUp() + self.product = self.env["product.product"].create( + { + "name": "Product", + "type": "product", + "categ_id": self.env.ref("product.product_category_all").id, + } + ) + self.location = self.env["stock.location"].create( + {"name": "Location", "usage": "internal"} + ) + self.quant = self.env["stock.quant"].create( + { + "product_id": self.product.id, + "location_id": self.location.id, + "quantity": 10.0, + } + ) + self.picking = self.env["stock.picking"].create( + { + "picking_type_id": self.env.ref("stock.picking_type_in").id, + "location_id": self.location.id, + "location_dest_id": self.env.ref("stock.stock_location_stock").id, + } + ) + self.move = self.env["stock.move"].create( + { + "name": "Move", + "product_id": self.product.id, + "product_uom_qty": 5.0, + "product_uom": self.product.uom_id.id, + "picking_id": self.picking.id, + "location_id": self.location.id, + "location_dest_id": self.env.ref("stock.stock_location_stock").id, + } + ) + + @classmethod + def _make_inventory(cls, product, location, qty): + cls.env["stock.quant"].with_context(inventory_mode=True).create( + { + "product_id": product.id, + "location_id": location.id, + "inventory_quantity": qty, + } + )._apply_inventory() + + def test_safe_inventory_qty_done(self): + self.picking.action_assign() + self.picking.move_line_ids.write({"qty_done": 5.0}) + self.env.company.stock_quant_no_inventory_if_being_picked = True + with self.assertRaisesRegexp( + UserError, + "You cannot update the quantity of a quant that is currently being picked", + ): + self._make_inventory(self.product, self.location, 5.0) + self.env.company.stock_quant_no_inventory_if_being_picked = False + self._make_inventory(self.product, self.location, 5.0) + + def test_safe_inventory_no_qty_done(self): + self.picking.action_assign() + self.env.company.stock_quant_no_inventory_if_being_picked = True + self._make_inventory(self.product, self.location, 5.0) diff --git a/stock_quant_safe_inventory/views/res_config_settings.xml b/stock_quant_safe_inventory/views/res_config_settings.xml new file mode 100644 index 000000000..50b794412 --- /dev/null +++ b/stock_quant_safe_inventory/views/res_config_settings.xml @@ -0,0 +1,31 @@ + + + + + res.config.settings.view + res.config.settings + + +
+
+
+ +
+
+
+
+
+
+
+