diff --git a/fieldservice_sale_stock_product_tags/README.rst b/fieldservice_sale_stock_product_tags/README.rst new file mode 100644 index 0000000000..0ec6660a51 --- /dev/null +++ b/fieldservice_sale_stock_product_tags/README.rst @@ -0,0 +1,103 @@ +======================================= +Field Service - Sale Stock Product Tags +======================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:daaa7c594ceadfc80161e693db3f2dfc9abaa08d866be5266f59d829096580d4 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Ffield--service-lightgray.png?logo=github + :target: https://github.com/OCA/field-service/tree/15.0/fieldservice_sale_stock_product_tags + :alt: OCA/field-service +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/field-service-15-0/field-service-15-0-fieldservice_sale_stock_product_tags + :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/field-service&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends Field Service (FSM) functionality by automatically assigning FSM tags to orders based on product template tags. + +**Features:** +- Automatically assigns FSM tags to open FSM orders based on products in linked stock moves. +- Ensures missing FSM tags are created dynamically. +- Supports manual tag assignment without being overwritten. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module, you need to: + +1. Navigate to **Field Service > Master Data > Products**. +2. Select a product or create a new one. +3. In the **General Information** tab, add a tag in the **Tags** field. +4. Navigate to **Sales**. +5. Create a new sale, add the product, and confirm it. +6. The linked FSM order will have the tag assigned automatically. +7. If no FSM tag matches the product tag, a new one will be created with the same name and color. + +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 +~~~~~~~ + +* APSL-Nagarro + +Contributors +~~~~~~~~~~~~ + +* `APSL-Nagarro `_: + * Patryk Pyczko + +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-ppyczko| image:: https://github.com/ppyczko.png?size=40px + :target: https://github.com/ppyczko + :alt: ppyczko + +Current `maintainer `__: + +|maintainer-ppyczko| + +This module is part of the `OCA/field-service `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/fieldservice_sale_stock_product_tags/__init__.py b/fieldservice_sale_stock_product_tags/__init__.py new file mode 100644 index 0000000000..31660d6a96 --- /dev/null +++ b/fieldservice_sale_stock_product_tags/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/fieldservice_sale_stock_product_tags/__manifest__.py b/fieldservice_sale_stock_product_tags/__manifest__.py new file mode 100644 index 0000000000..bdc31949c2 --- /dev/null +++ b/fieldservice_sale_stock_product_tags/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2025 Patryk Pyczko (APSL-Nagarro) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Field Service - Sale Stock Product Tags", + "version": "15.0.1.0.0", + "summary": "Automatically assigns FSM tags to FSM orders based on product template tags.", + "category": "Field Service", + "website": "https://github.com/OCA/field-service", + "author": "APSL-Nagarro, Odoo Community Association (OCA)", + "maintainers": ["ppyczko"], + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": ["fieldservice_sale_stock", "product_template_tags"], +} diff --git a/fieldservice_sale_stock_product_tags/i18n/ca.po b/fieldservice_sale_stock_product_tags/i18n/ca.po new file mode 100644 index 0000000000..f76d1cfa1a --- /dev/null +++ b/fieldservice_sale_stock_product_tags/i18n/ca.po @@ -0,0 +1,31 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fieldservice_sale_stock_product_tags +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-18 11:27+0000\n" +"PO-Revision-Date: 2025-03-18 11:27+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: fieldservice_sale_stock_product_tags +#: model:ir.model.fields,help:fieldservice_sale_stock_product_tags.field_fsm_order__tag_ids +msgid "Classify and analyze your orders" +msgstr "Classifica i analitza les teves comandes" + +#. module: fieldservice_sale_stock_product_tags +#: model:ir.model,name:fieldservice_sale_stock_product_tags.model_fsm_order +msgid "Field Service Order" +msgstr "Ordre de Servei de Camp" + +#. module: fieldservice_sale_stock_product_tags +#: model:ir.model.fields,field_description:fieldservice_sale_stock_product_tags.field_fsm_order__tag_ids +msgid "Tags" +msgstr "Etiquetes" diff --git a/fieldservice_sale_stock_product_tags/i18n/es.po b/fieldservice_sale_stock_product_tags/i18n/es.po new file mode 100644 index 0000000000..d5e3e52613 --- /dev/null +++ b/fieldservice_sale_stock_product_tags/i18n/es.po @@ -0,0 +1,31 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fieldservice_sale_stock_product_tags +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-18 11:27+0000\n" +"PO-Revision-Date: 2025-03-18 11:27+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: fieldservice_sale_stock_product_tags +#: model:ir.model.fields,help:fieldservice_sale_stock_product_tags.field_fsm_order__tag_ids +msgid "Classify and analyze your orders" +msgstr "Clasifica y analiza tus pedidos" + +#. module: fieldservice_sale_stock_product_tags +#: model:ir.model,name:fieldservice_sale_stock_product_tags.model_fsm_order +msgid "Field Service Order" +msgstr "Orden de Servicio de Campo" + +#. module: fieldservice_sale_stock_product_tags +#: model:ir.model.fields,field_description:fieldservice_sale_stock_product_tags.field_fsm_order__tag_ids +msgid "Tags" +msgstr "Etiquetas" diff --git a/fieldservice_sale_stock_product_tags/i18n/fieldservice_sale_stock_product_tags.pot b/fieldservice_sale_stock_product_tags/i18n/fieldservice_sale_stock_product_tags.pot new file mode 100644 index 0000000000..8d185b4efa --- /dev/null +++ b/fieldservice_sale_stock_product_tags/i18n/fieldservice_sale_stock_product_tags.pot @@ -0,0 +1,31 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fieldservice_sale_stock_product_tags +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-18 11:25+0000\n" +"PO-Revision-Date: 2025-03-18 11:25+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: fieldservice_sale_stock_product_tags +#: model:ir.model.fields,help:fieldservice_sale_stock_product_tags.field_fsm_order__tag_ids +msgid "Classify and analyze your orders" +msgstr "" + +#. module: fieldservice_sale_stock_product_tags +#: model:ir.model,name:fieldservice_sale_stock_product_tags.model_fsm_order +msgid "Field Service Order" +msgstr "" + +#. module: fieldservice_sale_stock_product_tags +#: model:ir.model.fields,field_description:fieldservice_sale_stock_product_tags.field_fsm_order__tag_ids +msgid "Tags" +msgstr "" diff --git a/fieldservice_sale_stock_product_tags/models/__init__.py b/fieldservice_sale_stock_product_tags/models/__init__.py new file mode 100644 index 0000000000..6a7ad2de8c --- /dev/null +++ b/fieldservice_sale_stock_product_tags/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import fsm_order diff --git a/fieldservice_sale_stock_product_tags/models/fsm_order.py b/fieldservice_sale_stock_product_tags/models/fsm_order.py new file mode 100644 index 0000000000..d0afc8ceea --- /dev/null +++ b/fieldservice_sale_stock_product_tags/models/fsm_order.py @@ -0,0 +1,42 @@ +# Copyright 2025 Patryk Pyczko (APSL-Nagarro) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class FSMOrder(models.Model): + _inherit = "fsm.order" + + tag_ids = fields.Many2many( + "fsm.tag", + "fsm_order_tag_rel", + "fsm_order_id", + "tag_id", + string="Tags", + compute="_compute_fsm_tags", + inverse="_inverse_fsm_tags", + store=True, + help="Classify and analyze your orders", + ) + + @api.depends("move_ids.product_id.tag_ids") + def _compute_fsm_tags(self): + FSMTag = self.env["fsm.tag"] + active_orders = self.filtered(lambda o: not o.stage_id.is_closed) + + for order in active_orders: + product_tags = order.move_ids.mapped("product_id.tag_ids") + + auto_tags = FSMTag.search([("name", "in", product_tags.mapped("name"))]) + missing_tags = product_tags.filtered( + lambda tag: tag.name not in auto_tags.mapped("name") + ) + + auto_tags |= FSMTag.create( + [{"name": tag.name, "color": tag.color or 10} for tag in missing_tags] + ) + + order.tag_ids |= auto_tags + + def _inverse_fsm_tags(self): + pass diff --git a/fieldservice_sale_stock_product_tags/readme/CONTRIBUTORS.rst b/fieldservice_sale_stock_product_tags/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..d9d047c2d6 --- /dev/null +++ b/fieldservice_sale_stock_product_tags/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* `APSL-Nagarro `_: + * Patryk Pyczko diff --git a/fieldservice_sale_stock_product_tags/readme/DESCRIPTION.rst b/fieldservice_sale_stock_product_tags/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..c1058b9746 --- /dev/null +++ b/fieldservice_sale_stock_product_tags/readme/DESCRIPTION.rst @@ -0,0 +1,6 @@ +This module extends Field Service (FSM) functionality by automatically assigning FSM tags to orders based on product template tags. + +**Features:** +- Automatically assigns FSM tags to open FSM orders based on products in linked stock moves. +- Ensures missing FSM tags are created dynamically. +- Supports manual tag assignment without being overwritten. diff --git a/fieldservice_sale_stock_product_tags/readme/USAGE.rst b/fieldservice_sale_stock_product_tags/readme/USAGE.rst new file mode 100644 index 0000000000..61acb85238 --- /dev/null +++ b/fieldservice_sale_stock_product_tags/readme/USAGE.rst @@ -0,0 +1,9 @@ +To use this module, you need to: + +1. Navigate to **Field Service > Master Data > Products**. +2. Select a product or create a new one. +3. In the **General Information** tab, add a tag in the **Tags** field. +4. Navigate to **Sales**. +5. Create a new sale, add the product, and confirm it. +6. The linked FSM order will have the tag assigned automatically. +7. If no FSM tag matches the product tag, a new one will be created with the same name and color. diff --git a/fieldservice_sale_stock_product_tags/static/description/icon.png b/fieldservice_sale_stock_product_tags/static/description/icon.png new file mode 100644 index 0000000000..955674d8f0 Binary files /dev/null and b/fieldservice_sale_stock_product_tags/static/description/icon.png differ diff --git a/fieldservice_sale_stock_product_tags/static/description/index.html b/fieldservice_sale_stock_product_tags/static/description/index.html new file mode 100644 index 0000000000..e75cd10b91 --- /dev/null +++ b/fieldservice_sale_stock_product_tags/static/description/index.html @@ -0,0 +1,444 @@ + + + + + +Field Service - Sale Stock Product Tags + + + +
+

Field Service - Sale Stock Product Tags

+ + +

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

+

This module extends Field Service (FSM) functionality by automatically assigning FSM tags to orders based on product template tags.

+

Features: +- Automatically assigns FSM tags to open FSM orders based on products in linked stock moves. +- Ensures missing FSM tags are created dynamically. +- Supports manual tag assignment without being overwritten.

+

Table of contents

+ +
+

Usage

+

To use this module, you need to:

+
    +
  1. Navigate to Field Service > Master Data > Products.
  2. +
  3. Select a product or create a new one.
  4. +
  5. In the General Information tab, add a tag in the Tags field.
  6. +
  7. Navigate to Sales.
  8. +
  9. Create a new sale, add the product, and confirm it.
  10. +
  11. The linked FSM order will have the tag assigned automatically.
  12. +
  13. If no FSM tag matches the product tag, a new one will be created with the same name and color.
  14. +
+
+
+

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

+
    +
  • APSL-Nagarro
  • +
+
+
+

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:

+

ppyczko

+

This module is part of the OCA/field-service project on GitHub.

+

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

+
+
+
+ + diff --git a/fieldservice_sale_stock_product_tags/tests/__init__.py b/fieldservice_sale_stock_product_tags/tests/__init__.py new file mode 100644 index 0000000000..77c8370662 --- /dev/null +++ b/fieldservice_sale_stock_product_tags/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_fsm_order_tags diff --git a/fieldservice_sale_stock_product_tags/tests/test_fsm_order_tags.py b/fieldservice_sale_stock_product_tags/tests/test_fsm_order_tags.py new file mode 100644 index 0000000000..bf77687faa --- /dev/null +++ b/fieldservice_sale_stock_product_tags/tests/test_fsm_order_tags.py @@ -0,0 +1,150 @@ +# Copyright 2025 Patryk Pyczko (APSL-Nagarro) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields +from odoo.tests.common import TransactionCase + + +class TestFSMOrderTags(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.FSMOrder = cls.env["fsm.order"] + cls.Move = cls.env["stock.move"] + cls.FSMTag = cls.env["fsm.tag"] + cls.ProductTag = cls.env["product.template.tag"] + cls.Picking = cls.env["stock.picking"] + + cls.product_10 = cls.env.ref("product.product_product_10") + cls.product_25 = cls.env.ref("product.product_product_25") + + cls.prod_tag1 = cls.ProductTag.create({"name": "Tag A"}) + cls.prod_tag2 = cls.ProductTag.create({"name": "Tag B"}) + cls.product_10.tag_ids |= cls.prod_tag1 + cls.product_25.tag_ids |= cls.prod_tag2 + + cls.test_location = cls.env.ref("fieldservice.test_location") + cls.fsm_order = cls.FSMOrder.create({"location_id": cls.test_location.id}) + + cls.picking = cls.Picking.create( + { + "name": "FSM Picking for Test", + "picking_type_id": cls.env.ref("stock.picking_type_out").id, + "location_id": cls.env.ref("stock.stock_location_stock").id, + "location_dest_id": cls.env.ref("stock.stock_location_customers").id, + "fsm_order_id": cls.fsm_order.id, + } + ) + + cls.move1 = cls.Move.create( + { + "name": "Move for Product 10", + "product_id": cls.product_10.id, + "product_uom_qty": 1.0, + "product_uom": cls.product_10.uom_id.id, + "location_id": cls.picking.location_id.id, + "location_dest_id": cls.picking.location_dest_id.id, + "picking_id": cls.picking.id, + } + ) + + cls.move2 = cls.Move.create( + { + "name": "Move for Product 25", + "product_id": cls.product_25.id, + "product_uom_qty": 1.0, + "product_uom": cls.product_25.uom_id.id, + "location_id": cls.picking.location_id.id, + "location_dest_id": cls.picking.location_dest_id.id, + "picking_id": cls.picking.id, + } + ) + + def _isp_account_installed(self): + """Checks if module is installed which will require more + logic for the tests. + :return Boolean indicating the installed status of the module + """ + result = False + isp_account_module = self.env["ir.module.module"].search( + [("name", "=", "fieldservice_isp_account")] + ) + if isp_account_module and isp_account_module.state == "installed": + result = True + return result + + def _fulfill_order(self, order): + """Extra logic required to fulfill FSM order status and prevent + validation error when attempting to complete the FSM order + :return FSM Order with additional fields set + """ + analytic_account = self.env.ref("analytic.analytic_administratif") + self.test_location.analytic_account_id = analytic_account.id + timesheet = self.env["account.analytic.line"].create( + { + "name": "timesheet_line", + "unit_amount": 1, + "account_id": analytic_account.id, + "user_id": self.env.ref("base.partner_admin").id, + "product_id": self.env.ref( + "fieldservice_isp_account.field_service_regular_time" + ).id, + } + ) + order.write( + { + "employee_timesheet_ids": [(6, 0, timesheet.ids)], + } + ) + return order + + def test_fsm_order_tags_assignment(self): + """Test that FSM order correctly assigns FSM tags based on product tags.""" + self.fsm_order._compute_picking_ids() + self.fsm_order._compute_fsm_tags() + + fsm_tag1 = self.FSMTag.search([("name", "=", "Tag A")], limit=1) + fsm_tag2 = self.FSMTag.search([("name", "=", "Tag B")], limit=1) + + self.assertIn(fsm_tag1, self.fsm_order.tag_ids, "Tag A should be assigned") + self.assertIn(fsm_tag2, self.fsm_order.tag_ids, "Tag B should be assigned") + + def test_manual_tag_persistence(self): + """Test that manually added FSM tags are preserved after computation.""" + manual_tag = self.FSMTag.create({"name": "Manual Tag", "color": 3}) + self.fsm_order.tag_ids |= manual_tag + + self.fsm_order._compute_fsm_tags() + + self.assertIn( + manual_tag, self.fsm_order.tag_ids, "Manual tag should be preserved" + ) + + def test_fsm_order_tags_not_updated_when_closed(self): + """Test that FSM order tags are not updated if the order is closed.""" + if self._isp_account_installed(): + self.fsm_order = self._fulfill_order(self.fsm_order) + + # Actual End and Resolution fields needed to complete fsm order + # when fieldservice_isp_flow module is installed + self.fsm_order.write( + { + "date_end": fields.Datetime.today(), + "resolution": "Work completed", + } + ) + + self.fsm_order.action_complete() + original_tags = self.fsm_order.tag_ids + + new_product_tag = self.ProductTag.create({"name": "New Tag"}) + self.product_10.tag_ids |= new_product_tag + + self.fsm_order._compute_fsm_tags() + + self.assertEqual( + original_tags, + self.fsm_order.tag_ids, + "Tags should not change when the FSM order is closed, even " + "if new product tags are added.", + ) diff --git a/setup/fieldservice_sale_stock_product_tags/odoo/addons/fieldservice_sale_stock_product_tags b/setup/fieldservice_sale_stock_product_tags/odoo/addons/fieldservice_sale_stock_product_tags new file mode 120000 index 0000000000..48e917d535 --- /dev/null +++ b/setup/fieldservice_sale_stock_product_tags/odoo/addons/fieldservice_sale_stock_product_tags @@ -0,0 +1 @@ +../../../../fieldservice_sale_stock_product_tags \ No newline at end of file diff --git a/setup/fieldservice_sale_stock_product_tags/setup.py b/setup/fieldservice_sale_stock_product_tags/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/fieldservice_sale_stock_product_tags/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)