diff --git a/product_state_shortage/README.rst b/product_state_shortage/README.rst new file mode 100644 index 00000000000..94e97846024 --- /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`` and ``stock.move`` models 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 stock receipts are validated. + +**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 new +stock is received. + +**Approach** + +The module introduces a boolean field ``is_shortage`` in the +``product.state`` model. When enabled, it triggers a reset of the +product state upon stock receipt validation. + +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 +automatically revert to the default state upon stock receipt validation. + +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. When stock receipts are validated, products in the shortage state + will automatically revert to the default state. + +This module does not impact the user interface beyond the described +configuration and usage steps. + +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/models/__init__.py b/product_state_shortage/models/__init__.py new file mode 100644 index 00000000000..b102c263890 --- /dev/null +++ b/product_state_shortage/models/__init__.py @@ -0,0 +1,3 @@ +from . import stock_move +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..e2ce018af7f --- /dev/null +++ b/product_state_shortage/models/product_state.py @@ -0,0 +1,14 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProductState(models.Model): + _inherit = "product.state" + + is_shortage = fields.Boolean( + string="Is Shortage State", + help="If checked, products in this state will automatically revert to the " + "default state when a stock receipt is validated.", + ) diff --git a/product_state_shortage/models/product_template.py b/product_state_shortage/models/product_template.py new file mode 100644 index 00000000000..3e981eb2e67 --- /dev/null +++ b/product_state_shortage/models/product_template.py @@ -0,0 +1,13 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import 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}) diff --git a/product_state_shortage/models/stock_move.py b/product_state_shortage/models/stock_move.py new file mode 100644 index 00000000000..d498a4a99cd --- /dev/null +++ b/product_state_shortage/models/stock_move.py @@ -0,0 +1,22 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class StockMove(models.Model): + _inherit = "stock.move" + + def _action_done(self, cancel_backorder=False): + res = super()._action_done(cancel_backorder=cancel_backorder) + + incoming_moves = self.filtered(lambda m: m.picking_code == "incoming") + if incoming_moves: + shortage_products_to_reset = incoming_moves.product_id.filtered( + lambda p: p.product_state_id.is_shortage + ) + + if shortage_products_to_reset: + shortage_products_to_reset.product_tmpl_id._reset_default_state() + + return res diff --git a/product_state_shortage/readme/CONFIGURE.md b/product_state_shortage/readme/CONFIGURE.md new file mode 100644 index 00000000000..8d556a8f776 --- /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 automatically revert to the default state upon stock receipt validation. diff --git a/product_state_shortage/readme/CONTEXT.md b/product_state_shortage/readme/CONTEXT.md new file mode 100644 index 00000000000..e55ae0427e1 --- /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 new stock is received. + +**Approach** + +The module introduces a boolean field `is_shortage` in the `product.state` model. When enabled, it triggers a reset of the product state upon stock receipt validation. 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..32b998c10be --- /dev/null +++ b/product_state_shortage/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module extends the `product.state` and `stock.move` models 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 stock receipts are validated. diff --git a/product_state_shortage/readme/USAGE.md b/product_state_shortage/readme/USAGE.md new file mode 100644 index 00000000000..9edca007e6e --- /dev/null +++ b/product_state_shortage/readme/USAGE.md @@ -0,0 +1,8 @@ +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. When stock receipts are validated, products in the shortage state will automatically revert to the default state. + +This module does not impact the user interface beyond the described configuration and usage steps. diff --git a/product_state_shortage/static/description/icon.png b/product_state_shortage/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/product_state_shortage/static/description/icon.png differ diff --git a/product_state_shortage/static/description/index.html b/product_state_shortage/static/description/index.html new file mode 100644 index 00000000000..9349eca3eb7 --- /dev/null +++ b/product_state_shortage/static/description/index.html @@ -0,0 +1,483 @@ + + + + + +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 and stock.move models 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 stock receipts are validated.

+

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 new +stock is received.

+

Approach

+

The module introduces a boolean field is_shortage in the +product.state model. When enabled, it triggers a reset of the +product state upon stock receipt validation.

+
+
+

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 +automatically revert to the default state upon stock receipt validation.

+
+
+

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. When stock receipts are validated, products in the shortage state +will automatically revert to the default state.
  8. +
+

This module does not impact the user interface beyond the described +configuration and usage steps.

+
+
+

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..aa88684c229 --- /dev/null +++ b/product_state_shortage/tests/test_product_state_shortage.py @@ -0,0 +1,129 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import Command +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} + ) + cls.loc_shelf_2 = cls.env["stock.location"].create( + {"name": "Shelf 2", "location_id": cls.wh.lot_stock_id.id} + ) + cls.loc_supplier = cls.env["stock.location"].search( + [("usage", "=", "supplier")], limit=1 + ) + cls.loc_customer = cls.env["stock.location"].search( + [("usage", "=", "customer")], limit=1 + ) + + def _create_picking(self, pick_type, product, qty, loc_from, loc_to): + picking = self.env["stock.picking"].create( + { + "picking_type_id": pick_type.id, + "move_ids": [ + Command.create( + { + "name": f"{product.name}: {self.loc_supplier.name} " + f"-> {self.wh.wh_input_stock_loc_id.name}", + "product_id": product.id, + "product_uom_qty": qty, + "product_uom": self.product.uom_id.id, + "location_id": loc_from.id, + "location_dest_id": loc_to.id, + } + ) + ], + } + ) + return picking + + def _do_picking(self, picking): + picking.action_confirm() + picking.action_assign() + picking.action_set_quantities_to_reservation() + picking._action_done() + + def _do_reception_picking(self, product, qty=1): + picking = self._create_picking( + pick_type=self.wh.in_type_id, + product=self.product, + qty=qty, + loc_from=self.loc_supplier, + loc_to=self.wh.wh_input_stock_loc_id, + ) + self._do_picking(picking) + + def test_receiving_new_stock_resets_shortage_state(self): + self.assertEqual(self.product.product_state_id, self.shortage_product_state) + self._do_reception_picking(self.product, qty=1) + self.assertEqual(self.product.product_state_id, self.default_product_state) + + def test_only_incoming_transfers_reset_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 + ) + + # Internal should not reset "shortage" + picking = self._create_picking( + pick_type=self.wh.int_type_id, + product=self.product, + qty=5, + loc_from=self.loc_shelf_1, + loc_to=self.loc_shelf_2, + ) + self._do_picking(picking) + self.assertEqual(self.product.product_state_id, self.shortage_product_state) + + # Outgoing should not reset "shortage" + picking = self._create_picking( + pick_type=self.wh.out_type_id, + product=self.product, + qty=5, + loc_from=self.loc_shelf_1, + loc_to=self.loc_customer, + ) + self._do_picking(picking) + self.assertEqual(self.product.product_state_id, self.shortage_product_state) + + def test_no_reset_if_not_shortage_state(self): + self.product.product_state_id = self.test_product_state + self.assertFalse(self.product.product_state_id.is_shortage) + self._do_reception_picking(self.product, qty=1) + # No reset since the state is not a "shortage" state + self.assertNotEqual(self.product.product_state_id, self.default_product_state) + + self.product.product_state_id = self.shortage_product_state + self._do_reception_picking(self.product, qty=1) + self.assertEqual(self.product.product_state_id, self.default_product_state) 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, +)