From 663646113d85ee929fbd60559dc3dd744651cea4 Mon Sep 17 00:00:00 2001 From: Nicolas Delbovier Date: Thu, 23 Apr 2026 11:40:16 +0200 Subject: [PATCH] [ADD] product_state_shortage: Introduce shortage state management --- product_state_shortage/README.rst | 136 +++++ product_state_shortage/__init__.py | 1 + product_state_shortage/__manifest__.py | 18 + product_state_shortage/data/ir_cron_data.xml | 16 + product_state_shortage/models/__init__.py | 2 + .../models/product_state.py | 26 + .../models/product_template.py | 30 ++ product_state_shortage/readme/CONFIGURE.md | 8 + product_state_shortage/readme/CONTEXT.md | 7 + product_state_shortage/readme/CONTRIBUTORS.md | 1 + product_state_shortage/readme/CREDITS.md | 3 + product_state_shortage/readme/DESCRIPTION.md | 1 + product_state_shortage/readme/USAGE.md | 6 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 484 ++++++++++++++++++ product_state_shortage/tests/__init__.py | 1 + .../tests/test_product_state_shortage.py | 59 +++ .../views/product_state.xml | 43 ++ .../odoo/addons/product_state_shortage | 1 + setup/product_state_shortage/setup.py | 6 + 20 files changed, 849 insertions(+) create mode 100644 product_state_shortage/README.rst create mode 100644 product_state_shortage/__init__.py create mode 100644 product_state_shortage/__manifest__.py create mode 100644 product_state_shortage/data/ir_cron_data.xml create mode 100644 product_state_shortage/models/__init__.py create mode 100644 product_state_shortage/models/product_state.py create mode 100644 product_state_shortage/models/product_template.py create mode 100644 product_state_shortage/readme/CONFIGURE.md create mode 100644 product_state_shortage/readme/CONTEXT.md create mode 100644 product_state_shortage/readme/CONTRIBUTORS.md create mode 100644 product_state_shortage/readme/CREDITS.md create mode 100644 product_state_shortage/readme/DESCRIPTION.md create mode 100644 product_state_shortage/readme/USAGE.md create mode 100644 product_state_shortage/static/description/icon.png create mode 100644 product_state_shortage/static/description/index.html create mode 100644 product_state_shortage/tests/__init__.py create mode 100644 product_state_shortage/tests/test_product_state_shortage.py create mode 100644 product_state_shortage/views/product_state.xml create mode 120000 setup/product_state_shortage/odoo/addons/product_state_shortage create mode 100644 setup/product_state_shortage/setup.py diff --git a/product_state_shortage/README.rst b/product_state_shortage/README.rst new file mode 100644 index 00000000000..1ca61e1868c --- /dev/null +++ b/product_state_shortage/README.rst @@ -0,0 +1,136 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +====================== +Product State Shortage +====================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:b09ee549fbc946d01a69212107d01ea12d27fa070ee391708bb4bbdbbcc5eddd + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/license-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%2Fproduct--attribute-lightgray.png?logo=github + :target: https://github.com/OCA/product-attribute/tree/16.0/product_state_shortage + :alt: OCA/product-attribute +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-attribute-16-0/product-attribute-16-0-product_state_shortage + :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/product-attribute&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the ``product.state`` model to manage product states +during stock operations. It introduces the concept of a shortage state, +allowing products to automatically revert to their default state when +their available quantity becomes positive. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +**Business Need** + +This module addresses the need to manage product states effectively in +scenarios where stock shortages occur. It ensures that products marked +as in shortage automatically revert to their default state when their +available quantity is updated to a positive value. + +**Approach** + +The module introduces a boolean field ``is_shortage`` in the +``product.state`` model. When enabled, a cron will watch for products +under this state and trigger a reset of the products state upon +detecting a positive available quantity. + +Configuration +============= + +To configure this module, you need to: + +1. Navigate to the **Inventory** module. +2. Go to **Sales > Configuration > Products > Product States**. +3. Create or edit a product state and enable the **Is Shortage State** + checkbox. +4. Save the changes. + +This configuration ensures that products in the specified state will be +watched by a cron to automatically revert to the default state when +their available quantity becomes positive. + +Usage +===== + +To use this module, you need to: + +1. Navigate to the **Inventory** module. +2. Go to **Sales > Configuration > Products > Product States** and + create a state with the **Is Shortage State** checkbox enabled. +3. Assign the shortage state to products as needed. +4. A cron will watch those products so that when the available quantity + of such a product becomes positive, it will automatically revert to + the default state. + +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 +------------ + +- Nicolas Delbovier nicolas.delbovier@acsone.com + (https://www.acsone.eu/) + +Other credits +------------- + +The development of this module has been financially supported by: + +- ACSONE SA/NV + +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/product-attribute `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_state_shortage/__init__.py b/product_state_shortage/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/product_state_shortage/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/product_state_shortage/__manifest__.py b/product_state_shortage/__manifest__.py new file mode 100644 index 00000000000..d1104991c31 --- /dev/null +++ b/product_state_shortage/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Product State Shortage", + "summary": """Enables to declare a product state as a "shortage" state. + Such a state will automatically resolve to default state whenever new stock + is received for this product avoiding the need for manual intervention""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/product-attribute", + "depends": ["product_state", "stock"], + "data": [ + "views/product_state.xml", + ], + "demo": [], +} diff --git a/product_state_shortage/data/ir_cron_data.xml b/product_state_shortage/data/ir_cron_data.xml new file mode 100644 index 00000000000..04a111ee83f --- /dev/null +++ b/product_state_shortage/data/ir_cron_data.xml @@ -0,0 +1,16 @@ + + + + Product: Reset Shortage States based on Available Stock + + code + model.cron_reset_shortage_states() + 1 + days + -1 + + + + diff --git a/product_state_shortage/models/__init__.py b/product_state_shortage/models/__init__.py new file mode 100644 index 00000000000..3e93acc6814 --- /dev/null +++ b/product_state_shortage/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_state +from . import product_template diff --git a/product_state_shortage/models/product_state.py b/product_state_shortage/models/product_state.py new file mode 100644 index 00000000000..41cede899b7 --- /dev/null +++ b/product_state_shortage/models/product_state.py @@ -0,0 +1,26 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class ProductState(models.Model): + _inherit = "product.state" + + is_shortage = fields.Boolean( + string="Is Shortage State", + help="If checked, products in this state will be processed by a " + "cron to revert to default state if stock is available.", + ) + + @api.constrains("is_shortage", "default") + def _check_shortage_not_default(self): + for record in self: + if record.is_shortage and record.default: + raise ValidationError( + _( + "A product state cannot be both the 'Default' state and " + "a 'Shortage' state at the same time." + ) + ) diff --git a/product_state_shortage/models/product_template.py b/product_state_shortage/models/product_template.py new file mode 100644 index 00000000000..065ca3485f3 --- /dev/null +++ b/product_state_shortage/models/product_template.py @@ -0,0 +1,30 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + def _reset_default_state(self): + if default_state := self._get_default_product_state(): + self.write({"product_state_id": default_state.id}) + + @api.model + def cron_reset_shortage_states(self): + """ + Finds products in a 'shortage' state that now have physical stock + and resets them to the default state. + """ + shortage_states = self.env["product.state"].search([("is_shortage", "=", True)]) + if not shortage_states: + return + + templates = self.search([("product_state_id", "in", shortage_states.ids)]) + + templates_to_reset = templates.filtered( + lambda t: any(p.qty_available > 0 for p in t.product_variant_ids) + ) + + templates_to_reset._reset_default_state() diff --git a/product_state_shortage/readme/CONFIGURE.md b/product_state_shortage/readme/CONFIGURE.md new file mode 100644 index 00000000000..f20bdba2bce --- /dev/null +++ b/product_state_shortage/readme/CONFIGURE.md @@ -0,0 +1,8 @@ +To configure this module, you need to: + +1. Navigate to the **Inventory** module. +2. Go to **Sales > Configuration > Products > Product States**. +3. Create or edit a product state and enable the **Is Shortage State** checkbox. +4. Save the changes. + +This configuration ensures that products in the specified state will be watched by a cron to automatically revert to the default state when their available quantity becomes positive. diff --git a/product_state_shortage/readme/CONTEXT.md b/product_state_shortage/readme/CONTEXT.md new file mode 100644 index 00000000000..d0edca80305 --- /dev/null +++ b/product_state_shortage/readme/CONTEXT.md @@ -0,0 +1,7 @@ +**Business Need** + +This module addresses the need to manage product states effectively in scenarios where stock shortages occur. It ensures that products marked as in shortage automatically revert to their default state when their available quantity is updated to a positive value. + +**Approach** + +The module introduces a boolean field `is_shortage` in the `product.state` model. When enabled, a cron will watch for products under this state and trigger a reset of the products state upon detecting a positive available quantity. diff --git a/product_state_shortage/readme/CONTRIBUTORS.md b/product_state_shortage/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..e5bfceea56c --- /dev/null +++ b/product_state_shortage/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Nicolas Delbovier (https://www.acsone.eu/) \ No newline at end of file diff --git a/product_state_shortage/readme/CREDITS.md b/product_state_shortage/readme/CREDITS.md new file mode 100644 index 00000000000..fa3dfb1d44c --- /dev/null +++ b/product_state_shortage/readme/CREDITS.md @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +- ACSONE SA/NV diff --git a/product_state_shortage/readme/DESCRIPTION.md b/product_state_shortage/readme/DESCRIPTION.md new file mode 100644 index 00000000000..d45769e3179 --- /dev/null +++ b/product_state_shortage/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module extends the `product.state` model to manage product states during stock operations. It introduces the concept of a shortage state, allowing products to automatically revert to their default state when their available quantity becomes positive. diff --git a/product_state_shortage/readme/USAGE.md b/product_state_shortage/readme/USAGE.md new file mode 100644 index 00000000000..b2f5ea89363 --- /dev/null +++ b/product_state_shortage/readme/USAGE.md @@ -0,0 +1,6 @@ +To use this module, you need to: + +1. Navigate to the **Inventory** module. +2. Go to **Sales > Configuration > Products > Product States** and create a state with the **Is Shortage State** checkbox enabled. +3. Assign the shortage state to products as needed. +4. A cron will watch those products so that when the available quantity of such a product becomes positive, it will automatically revert to the default state. diff --git a/product_state_shortage/static/description/icon.png b/product_state_shortage/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/product_state_shortage/static/description/index.html b/product_state_shortage/static/description/index.html new file mode 100644 index 00000000000..fa854649fe7 --- /dev/null +++ b/product_state_shortage/static/description/index.html @@ -0,0 +1,484 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Product State Shortage

+ +

Beta License: AGPL-3 OCA/product-attribute Translate me on Weblate Try me on Runboat

+

This module extends the product.state model to manage product states +during stock operations. It introduces the concept of a shortage state, +allowing products to automatically revert to their default state when +their available quantity becomes positive.

+

Table of contents

+ +
+

Use Cases / Context

+

Business Need

+

This module addresses the need to manage product states effectively in +scenarios where stock shortages occur. It ensures that products marked +as in shortage automatically revert to their default state when their +available quantity is updated to a positive value.

+

Approach

+

The module introduces a boolean field is_shortage in the +product.state model. When enabled, a cron will watch for products +under this state and trigger a reset of the products state upon +detecting a positive available quantity.

+
+
+

Configuration

+

To configure this module, you need to:

+
    +
  1. Navigate to the Inventory module.
  2. +
  3. Go to Sales > Configuration > Products > Product States.
  4. +
  5. Create or edit a product state and enable the Is Shortage State +checkbox.
  6. +
  7. Save the changes.
  8. +
+

This configuration ensures that products in the specified state will be +watched by a cron to automatically revert to the default state when +their available quantity becomes positive.

+
+
+

Usage

+

To use this module, you need to:

+
    +
  1. Navigate to the Inventory module.
  2. +
  3. Go to Sales > Configuration > Products > Product States and +create a state with the Is Shortage State checkbox enabled.
  4. +
  5. Assign the shortage state to products as needed.
  6. +
  7. A cron will watch those products so that when the available quantity +of such a product becomes positive, it will automatically revert to +the default state.
  8. +
+
+
+

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
  • +
+
+ +
+

Other credits

+

The development of this module has been financially supported by:

+
    +
  • 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/product-attribute project on GitHub.

+

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

+
+
+
+
+ + diff --git a/product_state_shortage/tests/__init__.py b/product_state_shortage/tests/__init__.py new file mode 100644 index 00000000000..dfc5bb75dca --- /dev/null +++ b/product_state_shortage/tests/__init__.py @@ -0,0 +1 @@ +from . import test_product_state_shortage diff --git a/product_state_shortage/tests/test_product_state_shortage.py b/product_state_shortage/tests/test_product_state_shortage.py new file mode 100644 index 00000000000..52c8ad3e2d2 --- /dev/null +++ b/product_state_shortage/tests/test_product_state_shortage.py @@ -0,0 +1,59 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase + + +class TestProductStateShortage(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.shortage_product_state = cls.env["product.state"].create( + { + "name": "Shortage", + "code": "S", + "is_shortage": True, + } + ) + cls.test_product_state = cls.env["product.state"].create( + { + "name": "Test", + "code": "T", + "is_shortage": False, + } + ) + cls.default_product_state = cls.env[ + "product.template" + ]._get_default_product_state() + cls.product = cls.env["product.product"].create( + { + "name": "Test Product", + "product_state_id": cls.shortage_product_state.id, + "detailed_type": "product", + } + ) + cls.wh = cls.env["stock.warehouse"].search([], limit=1) + cls.loc_shelf_1 = cls.env["stock.location"].create( + {"name": "Shelf 1", "location_id": cls.wh.lot_stock_id.id} + ) + + def test_qty_available_resets_shortage_state(self): + self.assertEqual(self.product.product_state_id, self.shortage_product_state) + self.env["stock.quant"]._update_available_quantity( + self.product, self.loc_shelf_1, 15.0 + ) + self.env["product.template"].cron_reset_shortage_states() + self.assertEqual(self.product.product_state_id, self.default_product_state) + + def test_no_reset_if_not_shortage_state(self): + self.product.product_state_id = self.test_product_state + self.env["stock.quant"]._update_available_quantity( + self.product, self.loc_shelf_1, 15.0 + ) + self.env["product.template"].cron_reset_shortage_states() + self.assertEqual(self.product.product_state_id, self.test_product_state) + + def test_cannot_be_shortage_and_default(self): + with self.assertRaises(ValidationError): + self.default_product_state.is_shortage = True diff --git a/product_state_shortage/views/product_state.xml b/product_state_shortage/views/product_state.xml new file mode 100644 index 00000000000..68c1e05ff89 --- /dev/null +++ b/product_state_shortage/views/product_state.xml @@ -0,0 +1,43 @@ + + + + + + product.state + + + + + + + + + + + + product.state + + + + + + + + + + + product.state + + + + + + + + + diff --git a/setup/product_state_shortage/odoo/addons/product_state_shortage b/setup/product_state_shortage/odoo/addons/product_state_shortage new file mode 120000 index 00000000000..4f531be194b --- /dev/null +++ b/setup/product_state_shortage/odoo/addons/product_state_shortage @@ -0,0 +1 @@ +../../../../product_state_shortage \ No newline at end of file diff --git a/setup/product_state_shortage/setup.py b/setup/product_state_shortage/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/product_state_shortage/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)