diff --git a/repair_stock_consumption_step/README.rst b/repair_stock_consumption_step/README.rst new file mode 100644 index 00000000..9e0f6540 --- /dev/null +++ b/repair_stock_consumption_step/README.rst @@ -0,0 +1,140 @@ +============================= +Repair Stock Consumption Step +============================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:93fc83002290646de5793f5425fca96699172ab4f9487b4196a3e8481bbaead6 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Frepair-lightgray.png?logo=github + :target: https://github.com/OCA/repair/tree/16.0/repair_stock_consumption_step + :alt: OCA/repair +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/repair-16-0/repair-16-0-repair_stock_consumption_step + :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/repair&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module introduces an optional intermediate step: + +- When enabled at warehouse level, repair consumption moves are grouped + into a stock picking. +- The repair order is set to a new **Consumption** state until the + picking is validated. +- Users can process the picking manually, assign lots/serials, and only + then complete the repair. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +In the base repair module, consumption moves for spare parts are created +and immediately validated when the repair order is marked as done. This +prevents user interaction such as choosing lot/serial numbers. + +Configuration +============= + +1. Go to **Inventory / Configuration / Warehouses**. + +2. Open the warehouse you want to use for repairs. + +3. + + 2. In **Technical info** : + + - Enable **Repair Consumption Step**. + - Select the **Repair Consumption Picking Type** (an internal + transfer type from the repair location to a production/virtual + location). + +Usage +===== + +1. Create a repair order with spare part lines. +2. Confirm the repair order. +3. Click **End Repair**: + + - If the warehouse setting is disabled: + + - Consumption moves are validated immediately, repair goes directly + to **Done**. + + - If the setting is enabled: + + - The repair order moves to the **Consumption** state. + - A stock picking is created for the spare part moves. + +4. Open the **Consumption Picking** from the repair order. +5. Process the picking: + + - Assign quantities and lots/serials. + - Validate the picking. + +6. Once the picking is validated, the repair order automatically moves + to **Done**. + +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 +------------ + +- Souheil Bejaoui souheil.bejaoui@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. + +.. |maintainer-sbejaoui| image:: https://github.com/sbejaoui.png?size=40px + :target: https://github.com/sbejaoui + :alt: sbejaoui + +Current `maintainer `__: + +|maintainer-sbejaoui| + +This module is part of the `OCA/repair `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/repair_stock_consumption_step/__init__.py b/repair_stock_consumption_step/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/repair_stock_consumption_step/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/repair_stock_consumption_step/__manifest__.py b/repair_stock_consumption_step/__manifest__.py new file mode 100644 index 00000000..b6be973b --- /dev/null +++ b/repair_stock_consumption_step/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Repair Stock Consumption Step", + "summary": """Adds a warehouse-configurable step to process repair consumption + moves in a picking""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/repair", + "maintainers": ["sbejaoui"], + "depends": ["repair", "repair_warehouse"], + "data": ["views/repair_order.xml", "views/stock_warehouse.xml"], + "demo": [], +} diff --git a/repair_stock_consumption_step/models/__init__.py b/repair_stock_consumption_step/models/__init__.py new file mode 100644 index 00000000..1bf803aa --- /dev/null +++ b/repair_stock_consumption_step/models/__init__.py @@ -0,0 +1,4 @@ +from . import stock_warehouse +from . import repair_order +from . import stock_move +from . import stock_picking diff --git a/repair_stock_consumption_step/models/repair_order.py b/repair_stock_consumption_step/models/repair_order.py new file mode 100644 index 00000000..7e6661c9 --- /dev/null +++ b/repair_stock_consumption_step/models/repair_order.py @@ -0,0 +1,72 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class RepairOrder(models.Model): + + _inherit = "repair.order" + + consumption_picking_id = fields.Many2one( + "stock.picking", string="Consumption Picking", readonly=True, copy=False + ) + repair_consumption_step = fields.Boolean( + related="warehouse_id.repair_consumption_step" + ) + state = fields.Selection( + selection_add=[("consumption", "Waiting Consumption")], + ondelete={"consumption": "set done"}, + ) + + def action_view_consumption_picking(self): + self.ensure_one() + return { + "name": "Repair Consumption Picking", + "type": "ir.actions.act_window", + "res_model": "stock.picking", + "view_mode": "form", + "res_id": self.consumption_picking_id.id, + } + + def action_repair_done(self): + need_consumption_step = self.filtered("repair_consumption_step") + res = super(RepairOrder, self - need_consumption_step).action_repair_done() + for rec in need_consumption_step: + rec_res = super( + RepairOrder, rec.with_context(dont_validate_repair_move=True) + ).action_repair_done() + res.update(rec_res) + moves = self.env["stock.move"].search([("repair_id", "=", rec.id)]) + if not moves: + continue + picking = self.env["stock.picking"].create( + { + "picking_type_id": rec.warehouse_id.repair_consumption_picking_type_id.id, + "origin": rec.name, + "location_id": moves[0].location_id.id, + "location_dest_id": moves[0].location_dest_id.id, + } + ) + moves.picking_id = picking.id + moves.move_line_ids.unlink() + moves._do_unreserve() + moves._action_confirm() + moves._action_assign() + moves.move_line_ids.picking_id = picking.id + rec.consumption_picking_id = picking + rec.state = "consumption" + return res + + def action_repair_end(self): + super().action_repair_end() + need_consumption_step = self.filtered("consumption_picking_id") + need_consumption_step.state = "consumption" + return True + + def _action_consumption_done(self): + for rec in self: + state = "done" + if not rec.invoice_id and rec.invoice_method == "after_repair": + state = "2binvoiced" + rec.state = state diff --git a/repair_stock_consumption_step/models/stock_move.py b/repair_stock_consumption_step/models/stock_move.py new file mode 100644 index 00000000..7dc0290d --- /dev/null +++ b/repair_stock_consumption_step/models/stock_move.py @@ -0,0 +1,18 @@ +# Copyright 2025 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): + repair_moves = self.browse() + if self.env.context.get("dont_validate_repair_move"): + repair_moves = self.filtered("repair_id") + + return super(StockMove, self - repair_moves)._action_done( + cancel_backorder=cancel_backorder + ) diff --git a/repair_stock_consumption_step/models/stock_picking.py b/repair_stock_consumption_step/models/stock_picking.py new file mode 100644 index 00000000..731b6219 --- /dev/null +++ b/repair_stock_consumption_step/models/stock_picking.py @@ -0,0 +1,21 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class StockPicking(models.Model): + + _inherit = "stock.picking" + + def _action_done(self): + res = super()._action_done() + done_pickings = self.filtered(lambda p: p.state == "done") + repair_orders = self.env["repair.order"].search( + [ + ("state", "=", "consumption"), + ("consumption_picking_id", "in", done_pickings.ids), + ] + ) + repair_orders._action_consumption_done() + return res diff --git a/repair_stock_consumption_step/models/stock_warehouse.py b/repair_stock_consumption_step/models/stock_warehouse.py new file mode 100644 index 00000000..db2c81a4 --- /dev/null +++ b/repair_stock_consumption_step/models/stock_warehouse.py @@ -0,0 +1,21 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class StockWarehouse(models.Model): + + _inherit = "stock.warehouse" + + repair_consumption_step = fields.Boolean( + string="Enable Repair Consumption Step", + help="If enabled, consumption moves from repairs will be grouped " + "in a picking instead of being directly validated.", + ) + repair_consumption_picking_type_id = fields.Many2one( + "stock.picking.type", + string="Repair Consumption Picking Type", + domain="[('code','=','internal'), ('warehouse_id','=', id)]", + help="Picking type used for repair consumption moves when the extra step is enabled.", + ) diff --git a/repair_stock_consumption_step/readme/CONFIGURE.md b/repair_stock_consumption_step/readme/CONFIGURE.md new file mode 100644 index 00000000..c7342fb9 --- /dev/null +++ b/repair_stock_consumption_step/readme/CONFIGURE.md @@ -0,0 +1,5 @@ +1. Go to **Inventory / Configuration / Warehouses**. +2. Open the warehouse you want to use for repairs. +3. 2. In **Technical info** : + - Enable **Repair Consumption Step**. + - Select the **Repair Consumption Picking Type** (an internal transfer type from the repair location to a production/virtual location). \ No newline at end of file diff --git a/repair_stock_consumption_step/readme/CONTEXT.md b/repair_stock_consumption_step/readme/CONTEXT.md new file mode 100644 index 00000000..da5dcc6b --- /dev/null +++ b/repair_stock_consumption_step/readme/CONTEXT.md @@ -0,0 +1,3 @@ +In the base repair module, consumption moves for spare parts are +created and immediately validated when the repair order is marked as done. +This prevents user interaction such as choosing lot/serial numbers. diff --git a/repair_stock_consumption_step/readme/CONTRIBUTORS.md b/repair_stock_consumption_step/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..dbdd727b --- /dev/null +++ b/repair_stock_consumption_step/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Souheil Bejaoui diff --git a/repair_stock_consumption_step/readme/DESCRIPTION.md b/repair_stock_consumption_step/readme/DESCRIPTION.md new file mode 100644 index 00000000..b141a0de --- /dev/null +++ b/repair_stock_consumption_step/readme/DESCRIPTION.md @@ -0,0 +1,8 @@ +This module introduces an optional intermediate step: + +- When enabled at warehouse level, repair consumption moves are grouped + into a stock picking. +- The repair order is set to a new **Consumption** state until the picking + is validated. +- Users can process the picking manually, assign lots/serials, and only + then complete the repair. \ No newline at end of file diff --git a/repair_stock_consumption_step/readme/USAGE.md b/repair_stock_consumption_step/readme/USAGE.md new file mode 100644 index 00000000..6e877ca9 --- /dev/null +++ b/repair_stock_consumption_step/readme/USAGE.md @@ -0,0 +1,13 @@ +1. Create a repair order with spare part lines. +2. Confirm the repair order. +3. Click **End Repair**: + - If the warehouse setting is disabled: + - Consumption moves are validated immediately, repair goes directly to **Done**. + - If the setting is enabled: + - The repair order moves to the **Consumption** state. + - A stock picking is created for the spare part moves. +4. Open the **Consumption Picking** from the repair order. +5. Process the picking: + - Assign quantities and lots/serials. + - Validate the picking. +6. Once the picking is validated, the repair order automatically moves to **Done**. \ No newline at end of file diff --git a/repair_stock_consumption_step/static/description/icon.png b/repair_stock_consumption_step/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/repair_stock_consumption_step/static/description/icon.png differ diff --git a/repair_stock_consumption_step/static/description/index.html b/repair_stock_consumption_step/static/description/index.html new file mode 100644 index 00000000..fb6cd611 --- /dev/null +++ b/repair_stock_consumption_step/static/description/index.html @@ -0,0 +1,489 @@ + + + + + +Repair Stock Consumption Step + + + +
+

Repair Stock Consumption Step

+ + +

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

+

This module introduces an optional intermediate step:

+
    +
  • When enabled at warehouse level, repair consumption moves are grouped +into a stock picking.
  • +
  • The repair order is set to a new Consumption state until the +picking is validated.
  • +
  • Users can process the picking manually, assign lots/serials, and only +then complete the repair.
  • +
+

Table of contents

+ +
+

Use Cases / Context

+

In the base repair module, consumption moves for spare parts are created +and immediately validated when the repair order is marked as done. This +prevents user interaction such as choosing lot/serial numbers.

+
+
+

Configuration

+
    +
  1. Go to Inventory / Configuration / Warehouses.

    +
  2. +
  3. Open the warehouse you want to use for repairs.

    +
  4. +
    1. +
    2. In Technical info :
    3. +
    +
      +
    • Enable Repair Consumption Step.
    • +
    • Select the Repair Consumption Picking Type (an internal +transfer type from the repair location to a production/virtual +location).
    • +
    +
  5. +
+
+
+

Usage

+
    +
  1. Create a repair order with spare part lines.
  2. +
  3. Confirm the repair order.
  4. +
  5. Click End Repair:
      +
    • If the warehouse setting is disabled:
        +
      • Consumption moves are validated immediately, repair goes directly +to Done.
      • +
      +
    • +
    • If the setting is enabled:
        +
      • The repair order moves to the Consumption state.
      • +
      • A stock picking is created for the spare part moves.
      • +
      +
    • +
    +
  6. +
  7. Open the Consumption Picking from the repair order.
  8. +
  9. Process the picking:
      +
    • Assign quantities and lots/serials.
    • +
    • Validate the picking.
    • +
    +
  10. +
  11. Once the picking is validated, the repair order automatically moves +to Done.
  12. +
+
+
+

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

+ +
+
+

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.

+

Current maintainer:

+

sbejaoui

+

This module is part of the OCA/repair project on GitHub.

+

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

+
+
+
+ + diff --git a/repair_stock_consumption_step/tests/__init__.py b/repair_stock_consumption_step/tests/__init__.py new file mode 100644 index 00000000..81a825dc --- /dev/null +++ b/repair_stock_consumption_step/tests/__init__.py @@ -0,0 +1 @@ +from . import test_repair_stock_consumption_step diff --git a/repair_stock_consumption_step/tests/test_repair_stock_consumption_step.py b/repair_stock_consumption_step/tests/test_repair_stock_consumption_step.py new file mode 100644 index 00000000..3966720f --- /dev/null +++ b/repair_stock_consumption_step/tests/test_repair_stock_consumption_step.py @@ -0,0 +1,99 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class TestRepairStockConsumptionStep(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partner = cls.env["res.partner"].create({"name": "Test Partner"}) + cls.product = cls.env["product.product"].create( + {"name": "Product to repair", "type": "product"} + ) + cls.product_c = cls.env["product.product"].create( + {"name": "product to consume", "type": "product"} + ) + cls.warehouse = cls.env["stock.warehouse"].create( + {"name": "WH", "code": "wh_test"} + ) + cls.repair_loc = cls.warehouse.lot_stock_id + cls.production_location = cls.env["stock.location"].search( + [("usage", "=", "production")], limit=1 + ) + cls.consumption_type = cls.env["stock.picking.type"].create( + { + "name": "Consumption", + "code": "internal", + "warehouse_id": cls.warehouse.id, + "sequence_code": "PREP", + "default_location_src_id": cls.repair_loc.id, + "default_location_dest_id": cls.production_location.id, + } + ) + cls.repair = cls.env["repair.order"].create( + { + "partner_id": cls.partner.id, + "product_id": cls.product.id, + "location_id": cls.repair_loc.id, + } + ) + cls.line = cls.env["repair.line"].create( + { + "name": "replace product", + "repair_id": cls.repair.id, + "type": "add", + "price_unit": 100, + "product_id": cls.product_c.id, + "product_uom_qty": 2.0, + "location_id": cls.repair_loc.id, + } + ) + cls.env["stock.quant"]._update_available_quantity( + cls.product, cls.repair_loc, 1.0 + ) + cls.env["stock.quant"]._update_available_quantity( + cls.product_c, cls.repair_loc, 10.0 + ) + cls.repair.action_validate() + cls.repair.action_repair_ready() + cls.repair.action_repair_start() + + @classmethod + def _do_picking(cls, picking): + for move in picking.move_ids: + move.quantity_done = move.product_qty + picking._action_done() + + def test_repair_done_no_consumption_step(self): + self.repair.action_repair_end() + moves = self.env["stock.move"].search([("repair_id", "=", self.repair.id)]) + self.assertTrue(moves) + self.assertTrue(all(m.state == "done" for m in moves)) + self.assertFalse(self.repair.consumption_picking_id) + + def test_repair_done_with_consumption_step(self): + self.warehouse.repair_consumption_step = True + self.warehouse.repair_consumption_picking_type_id = self.consumption_type + self.repair.action_repair_end() + self.assertEqual(self.repair.state, "consumption") + self.assertTrue(self.repair.consumption_picking_id) + moves = self.env["stock.move"].search([("repair_id", "=", self.repair.id)]) + pick = self.repair.consumption_picking_id + self.assertTrue(moves) + self.assertEqual(pick.move_ids, moves) + self.assertSetEqual(set(moves.mapped("state")), {"assigned"}) + self.assertIn(pick.state, "assigned") + self.assertEqual(pick.move_line_ids, moves.move_line_ids) + self._do_picking(pick) + self.assertEqual(self.repair.state, "done") + + def test_repair_done_with_consumption_step_invoice_after_repair(self): + self.warehouse.repair_consumption_step = True + self.warehouse.repair_consumption_picking_type_id = self.consumption_type + self.repair.invoice_method = "after_repair" + self.repair.action_repair_end() + self.assertEqual(self.repair.state, "consumption") + self._do_picking(self.repair.consumption_picking_id) + self.assertEqual(self.repair.state, "2binvoiced") diff --git a/repair_stock_consumption_step/views/repair_order.xml b/repair_stock_consumption_step/views/repair_order.xml new file mode 100644 index 00000000..575a5df2 --- /dev/null +++ b/repair_stock_consumption_step/views/repair_order.xml @@ -0,0 +1,26 @@ + + + + + + repair.order + + + + + + + + + + + diff --git a/repair_stock_consumption_step/views/stock_warehouse.xml b/repair_stock_consumption_step/views/stock_warehouse.xml new file mode 100644 index 00000000..0b412b7e --- /dev/null +++ b/repair_stock_consumption_step/views/stock_warehouse.xml @@ -0,0 +1,29 @@ + + + + + + stock.warehouse + + + + + + + + + + + + + + + + + + + diff --git a/setup/repair_stock_consumption_step/odoo/addons/repair_stock_consumption_step b/setup/repair_stock_consumption_step/odoo/addons/repair_stock_consumption_step new file mode 120000 index 00000000..0b5afade --- /dev/null +++ b/setup/repair_stock_consumption_step/odoo/addons/repair_stock_consumption_step @@ -0,0 +1 @@ +../../../../repair_stock_consumption_step \ No newline at end of file diff --git a/setup/repair_stock_consumption_step/setup.py b/setup/repair_stock_consumption_step/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/repair_stock_consumption_step/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..1dfe097c --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +odoo-addon-repair-warehouse @ git+https://github.com/OCA/repair.git@refs/pull/148/head#subdirectory=setup/repair_warehouse