diff --git a/setup/stock_available_exclude_location/odoo/addons/stock_available_exclude_location b/setup/stock_available_exclude_location/odoo/addons/stock_available_exclude_location new file mode 120000 index 000000000000..08d66a8ceba3 --- /dev/null +++ b/setup/stock_available_exclude_location/odoo/addons/stock_available_exclude_location @@ -0,0 +1 @@ +../../../../stock_available_exclude_location \ No newline at end of file diff --git a/setup/stock_available_exclude_location/setup.py b/setup/stock_available_exclude_location/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/stock_available_exclude_location/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_available_exclude_location/README.rst b/stock_available_exclude_location/README.rst new file mode 100644 index 000000000000..d85767277ead --- /dev/null +++ b/stock_available_exclude_location/README.rst @@ -0,0 +1,93 @@ +================================ +Stock Available Exclude Location +================================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:2f16c2fabd3459801329b642fc18f7ab592c16293e7ae5f42e8ea9b4de67143e + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |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/14.0/stock_available_exclude_location + :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-14-0/stock-logistics-warehouse-14-0-stock_available_exclude_location + :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=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allow defining excluded locations for product availability. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Go to **Inventory** > **Configuration** > **Settings**. + +- Find the Products section. +- In Excluded Locations for Availability, select the excluded locations. +- The selected locations and his children will be excluded from product availability. + +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 +~~~~~~~ + +* Escodoo + +Contributors +~~~~~~~~~~~~ + +* `Escodoo `_: + + * Marcel Savegnago + * Wesley Oliveira + +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_available_exclude_location/__init__.py b/stock_available_exclude_location/__init__.py new file mode 100644 index 000000000000..0650744f6bc6 --- /dev/null +++ b/stock_available_exclude_location/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_available_exclude_location/__manifest__.py b/stock_available_exclude_location/__manifest__.py new file mode 100644 index 000000000000..96d28dc5cbeb --- /dev/null +++ b/stock_available_exclude_location/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2024 - TODAY, Escodoo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Stock Available Exclude Location", + "summary": """ + Exclude locations for product available quantities""", + "version": "14.0.1.0.0", + "category": "Stock", + "license": "AGPL-3", + "author": "Escodoo,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "depends": [ + "stock_available_base_exclude_location", + "stock_location_children", + ], + "data": ["views/res_config_settings.xml"], + "installable": True, + "development_status": "Alpha", +} diff --git a/stock_available_exclude_location/models/__init__.py b/stock_available_exclude_location/models/__init__.py new file mode 100644 index 000000000000..2b1c03d90c01 --- /dev/null +++ b/stock_available_exclude_location/models/__init__.py @@ -0,0 +1,3 @@ +from . import product_product +from . import res_company +from . import res_config_settings diff --git a/stock_available_exclude_location/models/product_product.py b/stock_available_exclude_location/models/product_product.py new file mode 100644 index 000000000000..99258619a7eb --- /dev/null +++ b/stock_available_exclude_location/models/product_product.py @@ -0,0 +1,45 @@ +# Copyright 2024 - TODAY, Wesley Oliveira +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ProductProduct(models.Model): + + _inherit = "product.product" + + excluded_location_ids = fields.Many2many( + comodel_name="stock.location", + string="Locations to Exclude as Available", + compute="_compute_excluded_location_ids", + readonly=True, + ) + + @api.depends() + def _compute_excluded_location_ids(self): + self.update({"excluded_location_ids": False}) + stock_quants = self.env["stock.quant"].search( + [ + ("product_id", "in", self.ids), + ("on_hand", "=", True), + ] + ) + for product in self: + product_quants = stock_quants.filtered(lambda x: x.product_id == product) + if product_quants: + company_ids = product_quants.mapped(lambda x: x.company_id) + excluded_ids = company_ids.mapped( + lambda x: x.stock_excluded_location_ids + ) + if excluded_ids: + for excluded_location in excluded_ids: + excluded_ids |= excluded_location.children_ids + product.excluded_location_ids = excluded_ids + + def _compute_quantities_dict( + self, lot_id, owner_id, package_id, from_date=False, to_date=False + ): + context = dict(self._context, excluded_location_ids=self.excluded_location_ids) + return super( + ProductProduct, self.with_context(context) + )._compute_quantities_dict(lot_id, owner_id, package_id, from_date, to_date) diff --git a/stock_available_exclude_location/models/res_company.py b/stock_available_exclude_location/models/res_company.py new file mode 100644 index 000000000000..8722093089c7 --- /dev/null +++ b/stock_available_exclude_location/models/res_company.py @@ -0,0 +1,10 @@ +# Copyright 2024 - TODAY, Wesley Oliveira +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class ResCompany(models.Model): + + _name = "res.company" + _inherit = ["res.company", "stock.exclude.location.mixin"] diff --git a/stock_available_exclude_location/models/res_config_settings.py b/stock_available_exclude_location/models/res_config_settings.py new file mode 100644 index 000000000000..37327c583642 --- /dev/null +++ b/stock_available_exclude_location/models/res_config_settings.py @@ -0,0 +1,17 @@ +# Copyright 2024 - TODAY, Wesley Oliveira +# 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_excluded_location_ids = fields.Many2many( + comodel_name="stock.location", + related="company_id.stock_excluded_location_ids", + string="Excluded Locations for Product Availability", + help="Fill in this field to exclude locations for product available quantities.", + readonly=False, + ) diff --git a/stock_available_exclude_location/readme/CONFIGURE.rst b/stock_available_exclude_location/readme/CONFIGURE.rst new file mode 100644 index 000000000000..9d81a70dfe25 --- /dev/null +++ b/stock_available_exclude_location/readme/CONFIGURE.rst @@ -0,0 +1,5 @@ +Go to **Inventory** > **Configuration** > **Settings**. + +- Find the Products section. +- In Excluded Locations for Availability, select the excluded locations. +- The selected locations and his children will be excluded from product availability. diff --git a/stock_available_exclude_location/readme/CONTRIBUTORS.rst b/stock_available_exclude_location/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..9617554c2228 --- /dev/null +++ b/stock_available_exclude_location/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* `Escodoo `_: + + * Marcel Savegnago + * Wesley Oliveira diff --git a/stock_available_exclude_location/readme/DESCRIPTION.rst b/stock_available_exclude_location/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..c42b2e948436 --- /dev/null +++ b/stock_available_exclude_location/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allow defining excluded locations for product availability. diff --git a/stock_available_exclude_location/static/description/icon.png b/stock_available_exclude_location/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/stock_available_exclude_location/static/description/icon.png differ diff --git a/stock_available_exclude_location/static/description/index.html b/stock_available_exclude_location/static/description/index.html new file mode 100644 index 000000000000..117c36d7b7ee --- /dev/null +++ b/stock_available_exclude_location/static/description/index.html @@ -0,0 +1,443 @@ + + + + + +Stock Available Exclude Location + + + +
+

Stock Available Exclude Location

+ + +

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

+

This module allow defining excluded locations for product availability.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Configuration

+

Go to Inventory > Configuration > Settings.

+
    +
  • Find the Products section.
  • +
  • In Excluded Locations for Availability, select the excluded locations.
  • +
  • The selected locations and his children will be excluded from product availability.
  • +
+
+
+

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

+
    +
  • Escodoo
  • +
+
+
+

Contributors

+ +
+
+

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_available_exclude_location/tests/__init__.py b/stock_available_exclude_location/tests/__init__.py new file mode 100644 index 000000000000..d0e460e00e29 --- /dev/null +++ b/stock_available_exclude_location/tests/__init__.py @@ -0,0 +1 @@ +from . import test_exclude_location diff --git a/stock_available_exclude_location/tests/test_exclude_location.py b/stock_available_exclude_location/tests/test_exclude_location.py new file mode 100644 index 000000000000..c6e038983da4 --- /dev/null +++ b/stock_available_exclude_location/tests/test_exclude_location.py @@ -0,0 +1,79 @@ +# Copyright 2024 - TODAY, Wesley Oliveira +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests import SavepointCase + + +class TestExcludeLocation(SavepointCase): + @classmethod + def setUpClass(cls): + super(TestExcludeLocation, cls).setUpClass() + cls.company = cls.env.company + cls.warehouse = cls.env["stock.warehouse"].search( + [("company_id", "=", cls.company.id)] + ) + cls.location_1 = cls.env["stock.location"].create( + { + "company_id": cls.company.id, + "location_id": cls.warehouse.lot_stock_id.id, + "name": "Location 1", + } + ) + cls.location_2 = cls.env["stock.location"].create( + { + "company_id": cls.company.id, + "location_id": cls.warehouse.lot_stock_id.id, + "name": "Location 2", + } + ) + cls.sub_location_1 = cls.env["stock.location"].create( + { + "company_id": cls.company.id, + "location_id": cls.location_1.id, + "name": "Sub Location 1", + } + ) + cls.sub_location_2 = cls.env["stock.location"].create( + { + "company_id": cls.company.id, + "location_id": cls.location_2.id, + "name": "Sub Location 2", + } + ) + cls.product = cls.env["product.product"].create( + { + "name": "Test Product", + "type": "product", + } + ) + + def _add_stock_to_product(self, product, location, qty): + """ + Set the stock quantity of the product + :param product: product.product recordset + :param location: stock.location recordset + :param qty: float + """ + self.env["stock.quant"].with_context(inventory_mode=True).create( + { + "product_id": product.id, + "location_id": location.id, + "inventory_quantity": qty, + } + ) + + def test_exclude_location(self): + # Add stock for the product and query product stock availability normally. + self.company.stock_excluded_location_ids = False + self._add_stock_to_product(self.product, self.location_1, 50.0) + self._add_stock_to_product(self.product, self.sub_location_1, 20.0) + self._add_stock_to_product(self.product, self.location_2, 20.0) + self._add_stock_to_product(self.product, self.sub_location_2, 10.0) + self.assertEqual(self.product.qty_available, 100) + + # Add location_2 as an excluded location in the company, so location_2 + # and his child sub_location_2 are excluded from product availability. + self.company.stock_excluded_location_ids = self.location_2 + self.product._compute_excluded_location_ids() + self.product._compute_quantities() + self.assertEqual(self.product.qty_available, 70) diff --git a/stock_available_exclude_location/views/res_config_settings.xml b/stock_available_exclude_location/views/res_config_settings.xml new file mode 100644 index 000000000000..10158ede51db --- /dev/null +++ b/stock_available_exclude_location/views/res_config_settings.xml @@ -0,0 +1,39 @@ + + + + + + res.config.settings.view.form.inherit.stock (in stock_available_exclude_location) + res.config.settings + + + +
+
+
+ Excluded Locations for Availability +
+ Define the locations that will be excluded from product availability. +
+
+ +
+
+
+
+
+
+ +