diff --git a/repair_service/README.rst b/repair_service/README.rst new file mode 100644 index 00000000..b16bdc97 --- /dev/null +++ b/repair_service/README.rst @@ -0,0 +1,103 @@ +============== +Repair Service +============== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:ac26551a9ce89d1e4c4a02728cea444025c7f7ebc7ed6fa7553f91d625a9840a + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/17.0/repair_service + :alt: OCA/repair +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/repair-17-0/repair-17-0-repair_service + :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=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module introduces repair services. It adds a new "Services" tab in +the repair orders, where users can add and manage services associated +with the repair. Once the repair order is confirmed and a sale order is +created, these services can be automatically added as sale order lines. +Moreover, if the repair order has the "Under Warranty" field checked, +the module ensures that the corresponding sale order lines price are set +to a value of 0, reflecting the warranty coverage. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +This functionality is similar to the "Operations" tab that existed in +previous versions in core Odoo. This tab allowed users to manage +services and operations in the repair orders. This module reintroduces +that functionality and adds these services as sale order lines when the +sale order is generated. + +Usage +===== + +When creating or editing a repair order, you will find a new "Services" +tab within the repair order form. In this tab, you can input and manage +the services performed as part of the repair order. Once the repair +order is confirmed, when you creating a sale order from the repair +order, the services listed in the "Services" tab will be automatically +added as sale order lines. + +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 +------- + +* ForgeFlow + +Contributors +------------ + +- `ForgeFlow `__: + + - Andreu Orensanz + +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/repair `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/repair_service/__init__.py b/repair_service/__init__.py new file mode 100644 index 00000000..31660d6a --- /dev/null +++ b/repair_service/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/repair_service/__manifest__.py b/repair_service/__manifest__.py new file mode 100644 index 00000000..247bc9c9 --- /dev/null +++ b/repair_service/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2024 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Repair Service", + "summary": """ + Adds services to repair orders, so that they can be added + as sale order lines. + """, + "version": "17.0.1.0.0", + "category": "Repair", + "website": "https://github.com/OCA/repair", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "license": "AGPL-3", + "depends": ["repair"], + "data": ["security/ir.model.access.csv", "views/repair_views.xml"], +} diff --git a/repair_service/models/__init__.py b/repair_service/models/__init__.py new file mode 100644 index 00000000..c5c31165 --- /dev/null +++ b/repair_service/models/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import repair_service +from . import repair_order diff --git a/repair_service/models/repair_order.py b/repair_service/models/repair_order.py new file mode 100644 index 00000000..98458760 --- /dev/null +++ b/repair_service/models/repair_order.py @@ -0,0 +1,17 @@ +# Copyright 2024 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class RepairOrder(models.Model): + _inherit = "repair.order" + + repair_service_ids = fields.One2many( + "repair.service", "repair_id", "Services", copy=True + ) + + def action_create_sale_order(self): + action = super().action_create_sale_order() + self.repair_service_ids._create_repair_sale_order_line() + return action diff --git a/repair_service/models/repair_service.py b/repair_service/models/repair_service.py new file mode 100644 index 00000000..31b630a2 --- /dev/null +++ b/repair_service/models/repair_service.py @@ -0,0 +1,70 @@ +# Copyright 2024 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class RepairService(models.Model): + _name = "repair.service" + + repair_id = fields.Many2one( + "repair.order", "Repair Order Reference", ondelete="cascade", required=True + ) + display_name = fields.Text( + "Description", + required=True, + compute="_compute_name", + store=True, + precompute=True, + ) + product_id = fields.Many2one( + "product.product", "Product", domain=[("type", "=", "service")], required=True + ) + product_uom_category_id = fields.Many2one(related="product_id.uom_id.category_id") + product_uom = fields.Many2one( + "uom.uom", + required=True, + domain="[('category_id', '=', product_uom_category_id)]", + readonly=False, + precompute=True, + compute="_compute_product_uom", + store=True, + ) + product_uom_qty = fields.Float( + "Quantity", digits="Product Unit of Measure", required=True, default=1.0 + ) + + @api.depends("product_id") + def _compute_name(self): + for service in self: + service.display_name = service.product_id.name + + @api.depends("product_id") + def _compute_product_uom(self): + for service in self: + service.product_uom = service.product_id.uom_id + + def _create_repair_sale_order_line(self): + if not self: + return + so_line_vals = [] + for service in self: + if not service.repair_id.sale_order_id: + continue + product_qty = ( + service.product_uom_qty + if service.repair_id.state != "done" + else service.product_uom_qty + ) + so_line_vals.append( + { + "order_id": service.repair_id.sale_order_id.id, + "product_id": service.product_id.id, + "product_uom_qty": product_qty, + } + ) + if service.repair_id.under_warranty: + so_line_vals[-1]["price_unit"] = 0.0 + elif service.product_id.lst_price: + so_line_vals[-1]["price_unit"] = service.product_id.lst_price + self.env["sale.order.line"].create(so_line_vals) diff --git a/repair_service/pyproject.toml b/repair_service/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/repair_service/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/repair_service/readme/CONTEXT.md b/repair_service/readme/CONTEXT.md new file mode 100644 index 00000000..1cba7ff9 --- /dev/null +++ b/repair_service/readme/CONTEXT.md @@ -0,0 +1 @@ +This functionality is similar to the "Operations" tab that existed in previous versions in core Odoo. This tab allowed users to manage services and operations in the repair orders. This module reintroduces that functionality and adds these services as sale order lines when the sale order is generated. diff --git a/repair_service/readme/CONTRIBUTORS.md b/repair_service/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..650a4c13 --- /dev/null +++ b/repair_service/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- [ForgeFlow](https://forgeflow.com): + + > - Andreu Orensanz \<\> \ No newline at end of file diff --git a/repair_service/readme/DESCRIPTION.md b/repair_service/readme/DESCRIPTION.md new file mode 100644 index 00000000..854916ee --- /dev/null +++ b/repair_service/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module introduces repair services. It adds a new "Services" tab in the repair orders, where users can add and manage services associated with the repair. Once the repair order is confirmed and a sale order is created, these services can be automatically added as sale order lines. Moreover, if the repair order has the "Under Warranty" field checked, the module ensures that the corresponding sale order lines price are set to a value of 0, reflecting the warranty coverage. diff --git a/repair_service/readme/USAGE.md b/repair_service/readme/USAGE.md new file mode 100644 index 00000000..9187238a --- /dev/null +++ b/repair_service/readme/USAGE.md @@ -0,0 +1 @@ +When creating or editing a repair order, you will find a new "Services" tab within the repair order form. In this tab, you can input and manage the services performed as part of the repair order. Once the repair order is confirmed, when you creating a sale order from the repair order, the services listed in the "Services" tab will be automatically added as sale order lines. diff --git a/repair_service/security/ir.model.access.csv b/repair_service/security/ir.model.access.csv new file mode 100644 index 00000000..76c012f6 --- /dev/null +++ b/repair_service/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_repair_service_user,repair.service user,model_repair_service,stock.group_stock_user,1,1,1,1 diff --git a/repair_service/static/description/index.html b/repair_service/static/description/index.html new file mode 100644 index 00000000..e95a7135 --- /dev/null +++ b/repair_service/static/description/index.html @@ -0,0 +1,451 @@ + + + + + +Repair Service + + + +
+

Repair Service

+ + +

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

+

This module introduces repair services. It adds a new “Services” tab in +the repair orders, where users can add and manage services associated +with the repair. Once the repair order is confirmed and a sale order is +created, these services can be automatically added as sale order lines. +Moreover, if the repair order has the “Under Warranty” field checked, +the module ensures that the corresponding sale order lines price are set +to a value of 0, reflecting the warranty coverage.

+

Table of contents

+ +
+

Use Cases / Context

+

This functionality is similar to the “Operations” tab that existed in +previous versions in core Odoo. This tab allowed users to manage +services and operations in the repair orders. This module reintroduces +that functionality and adds these services as sale order lines when the +sale order is generated.

+
+
+

Usage

+

When creating or editing a repair order, you will find a new “Services” +tab within the repair order form. In this tab, you can input and manage +the services performed as part of the repair order. Once the repair +order is confirmed, when you creating a sale order from the repair +order, the services listed in the “Services” tab will be automatically +added as sale order lines.

+
+
+

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

+
    +
  • ForgeFlow
  • +
+
+
+

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/repair project on GitHub.

+

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

+
+
+
+ + diff --git a/repair_service/tests/__init__.py b/repair_service/tests/__init__.py new file mode 100644 index 00000000..94d9b034 --- /dev/null +++ b/repair_service/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_repair_service diff --git a/repair_service/tests/test_repair_service.py b/repair_service/tests/test_repair_service.py new file mode 100644 index 00000000..3f5c484e --- /dev/null +++ b/repair_service/tests/test_repair_service.py @@ -0,0 +1,103 @@ +from odoo.tests import tagged +from odoo.tests.common import TransactionCase + + +@tagged("post_install", "-at_install") +class TestRepairOrderFlow(TransactionCase): + def setUp(self): + super().setUp() + self.RepairOrder = self.env["repair.order"] + self.RepairService = self.env["repair.service"] + self.SaleOrder = self.env["sale.order"] + self.Product = self.env["product.product"] + self.Uom = self.env["uom.uom"] + + # Create a test service product + self.service_product = self.Product.create( + { + "name": "Repair Service Product", + "type": "service", + "uom_id": self.env.ref("uom.product_uom_unit").id, + } + ) + + # Create a test repair order + self.repair_order = self.RepairOrder.create( + { + "name": "Test Repair Order", + "partner_id": self.env.ref("base.res_partner_1").id, + } + ) + + # Create a repair service associated with the repair order + self.repair_service = self.RepairService.create( + { + "repair_id": self.repair_order.id, + "product_id": self.service_product.id, + "product_uom_qty": 2.0, + "product_uom": self.service_product.uom_id.id, + } + ) + + def test_01_action_create_sale_order(self): + # Create a sale order from the repair order + self.repair_order.action_create_sale_order() + + # Check that the sale order has been created + sale_order = self.repair_order.sale_order_id + self.assertTrue(sale_order) + + # Check that the sale order has a line for the repair service + sale_order_line = sale_order.order_line.filtered( + lambda lam: lam.product_id == self.service_product + ) + self.assertTrue(sale_order_line) + + # Check that the sale order line has the correct quantity + self.assertEqual(sale_order_line.product_uom_qty, 2.0) + + def test_02_action_create_sale_order_under_warranty(self): + # Set the repair order to be under warranty + self.repair_order.under_warranty = True + + # Create a sale order from the repair order + self.repair_order.action_create_sale_order() + + # Check that the sale order has been created + sale_order = self.repair_order.sale_order_id + self.assertTrue(sale_order) + + # Check that the sale order has a line for the repair service + sale_order_line = sale_order.order_line.filtered( + lambda lam: lam.product_id == self.service_product + ) + self.assertTrue(sale_order_line) + + # Check that the sale order line has the correct quantity + self.assertEqual(sale_order_line.product_uom_qty, 2.0) + + # Check that the sale order line has a price unit of 0.0 + self.assertEqual(sale_order_line.price_unit, 0.0) + + def test_03_action_create_sale_order_not_under_warranty(self): + # Set the repair order to be not under warranty + self.repair_order.under_warranty = False + + # Create a sale order from the repair order + self.repair_order.action_create_sale_order() + + # Check that the sale order has been created + sale_order = self.repair_order.sale_order_id + self.assertTrue(sale_order) + + # Check that the sale order has a line for the repair service + sale_order_line = sale_order.order_line.filtered( + lambda lam: lam.product_id == self.service_product + ) + self.assertTrue(sale_order_line) + + # Check that the sale order line has the correct quantity + self.assertEqual(sale_order_line.product_uom_qty, 2.0) + + # Check that the sale order line has the correct price unit + self.assertEqual(sale_order_line.price_unit, self.service_product.lst_price) diff --git a/repair_service/views/repair_views.xml b/repair_service/views/repair_views.xml new file mode 100644 index 00000000..ddb8be75 --- /dev/null +++ b/repair_service/views/repair_views.xml @@ -0,0 +1,29 @@ + + + + repair.order.form.inherit.operations - repair_service + repair.order + + + + + + + + + + + + + + + + + +