diff --git a/edi_project_oca/README.rst b/edi_project_oca/README.rst new file mode 100644 index 000000000..fd76940a1 --- /dev/null +++ b/edi_project_oca/README.rst @@ -0,0 +1,137 @@ +=========== +Edi Project +=========== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:c50da2c108f9f404566d0cf21a1cbf15c1d508f761b9841d1f1a99abad4e80b1 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi--framework-lightgray.png?logo=github + :target: https://github.com/OCA/edi-framework/tree/17.0/edi_project_oca + :alt: OCA/edi-framework +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/edi-framework-17-0/edi-framework-17-0-edi_project_oca + :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/edi-framework&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module intends to create a base to be extended by local EDI rules +for project management. + +In order to add customizations for projects, create a listener: + +.. code:: python + + from odoo.addons.component.core import Component + + + class ProjectEventListenerExample(Component): + _name = "project.project.event.listener.example" + _inherit = "base.event.listener" + _apply_on = ["project.project"] + + def on_project_create(self, project, vals: dict): + """Do stuff after the project has been created""" + + def on_project_write(self, project, vals: dict): + """Do stuff after the project has been updated""" + + def on_project_unlink(self, project): + """Do stuff before the project gets deleted""" + +In order to add customizations for tasks, create a listener: + +.. code:: python + + from odoo.addons.component.core import Component + + + class ProjectTaskEventListenerExample(Component): + _name = "project.task.event.listener.example" + _inherit = "base.event.listener" + _apply_on = ["project.task"] + + def on_task_create(self, task, vals: dict): + """Do stuff after the task has been created""" + + def on_task_write(self, task, vals: dict): + """Do stuff after the task has been updated""" + + def on_task_unlink(self, task): + """Do stuff before the task gets deleted""" + +Use ``@skip_if()`` decorator to avoid triggering a listener's method if +necessary: + +.. code:: python + + from odoo.addons.component.core import Component + from odoo.addons.component_event import skip_if + + + class ProjectTaskEventListenerExample(Component): + _name = "project.task.event.listener.example" + _inherit = "base.event.listener" + _apply_on = ["project.task"] + + @skip_if(lambda self, task: not task.stage_id) # Do nothing if the task has no stage + def on_task_create(self, task): + """Do stuff after the task has been created""" + +**Table of contents** + +.. contents:: + :local: + +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 +------- + +* Camptocamp + +Contributors +------------ + +- Silvio Gregorini + +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/edi-framework `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/edi_project_oca/__init__.py b/edi_project_oca/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/edi_project_oca/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/edi_project_oca/__manifest__.py b/edi_project_oca/__manifest__.py new file mode 100644 index 000000000..75d2cc130 --- /dev/null +++ b/edi_project_oca/__manifest__.py @@ -0,0 +1,28 @@ +# Copyright 2024 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +{ + "name": "Edi Project", + "summary": """ + Define EDI Configuration for Projects and Tasks + """, + "version": "17.0.1.0.0", + "license": "LGPL-3", + "author": "Camptocamp,Odoo Community Association (OCA)", + "development_status": "Beta", + "website": "https://github.com/OCA/edi-framework", + "depends": [ + # Odoo addons + "project", + # OCA/connector + "component_event", + # OCA/edi-framework + "edi_oca", + ], + "data": [ + "views/edi_exchange_record.xml", + "views/project_project.xml", + "views/project_task.xml", + ], + "demo": [], +} diff --git a/edi_project_oca/models/__init__.py b/edi_project_oca/models/__init__.py new file mode 100644 index 000000000..212818896 --- /dev/null +++ b/edi_project_oca/models/__init__.py @@ -0,0 +1,2 @@ +from . import project_project +from . import project_task diff --git a/edi_project_oca/models/project_project.py b/edi_project_oca/models/project_project.py new file mode 100644 index 000000000..8ea4ff15c --- /dev/null +++ b/edi_project_oca/models/project_project.py @@ -0,0 +1,29 @@ +# Copyright 2024 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import api, fields, models + + +class ProjectProject(models.Model): + _name = "project.project" + _inherit = ["project.project", "edi.exchange.consumer.mixin"] + + edi_disable_auto = fields.Boolean() + + @api.model_create_multi + def create(self, vals_list): + projects = super().create(vals_list) + for project, vals in zip(projects, vals_list, strict=True): + project._event("on_project_create").notify(project, vals) + return projects + + def write(self, vals): + res = super().write(vals) + for project in self: + project._event("on_project_write").notify(project, vals) + return res + + def unlink(self): + for project in self: + project._event("on_project_unlink").notify(project) + return super().unlink() diff --git a/edi_project_oca/models/project_task.py b/edi_project_oca/models/project_task.py new file mode 100644 index 000000000..216dc5da5 --- /dev/null +++ b/edi_project_oca/models/project_task.py @@ -0,0 +1,29 @@ +# Copyright 2024 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import api, fields, models + + +class ProjectTask(models.Model): + _name = "project.task" + _inherit = ["project.task", "edi.exchange.consumer.mixin"] + + edi_disable_auto = fields.Boolean() + + @api.model_create_multi + def create(self, vals_list): + tasks = super().create(vals_list) + for task, vals in zip(tasks, vals_list, strict=True): + task._event("on_task_create").notify(task, vals) + return tasks + + def write(self, vals): + res = super().write(vals) + for task in self: + task._event("on_task_write").notify(task, vals) + return res + + def unlink(self): + for task in self: + task._event("on_task_unlink").notify(task) + return super().unlink() diff --git a/edi_project_oca/pyproject.toml b/edi_project_oca/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/edi_project_oca/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/edi_project_oca/readme/CONTRIBUTORS.md b/edi_project_oca/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..82b9a91c1 --- /dev/null +++ b/edi_project_oca/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Silvio Gregorini \<\> diff --git a/edi_project_oca/readme/DESCRIPTION.md b/edi_project_oca/readme/DESCRIPTION.md new file mode 100644 index 000000000..1d97c67f0 --- /dev/null +++ b/edi_project_oca/readme/DESCRIPTION.md @@ -0,0 +1,61 @@ +This module intends to create a base to be extended by local EDI rules +for project management. + +In order to add customizations for projects, create a listener: + +```python +from odoo.addons.component.core import Component + + +class ProjectEventListenerExample(Component): + _name = "project.project.event.listener.example" + _inherit = "base.event.listener" + _apply_on = ["project.project"] + + def on_project_create(self, project, vals: dict): + """Do stuff after the project has been created""" + + def on_project_write(self, project, vals: dict): + """Do stuff after the project has been updated""" + + def on_project_unlink(self, project): + """Do stuff before the project gets deleted""" +``` + +In order to add customizations for tasks, create a listener: + +```python +from odoo.addons.component.core import Component + + +class ProjectTaskEventListenerExample(Component): + _name = "project.task.event.listener.example" + _inherit = "base.event.listener" + _apply_on = ["project.task"] + + def on_task_create(self, task, vals: dict): + """Do stuff after the task has been created""" + + def on_task_write(self, task, vals: dict): + """Do stuff after the task has been updated""" + + def on_task_unlink(self, task): + """Do stuff before the task gets deleted""" +``` + +Use ``@skip_if()`` decorator to avoid triggering a listener's method if necessary: + +```python +from odoo.addons.component.core import Component +from odoo.addons.component_event import skip_if + + +class ProjectTaskEventListenerExample(Component): + _name = "project.task.event.listener.example" + _inherit = "base.event.listener" + _apply_on = ["project.task"] + + @skip_if(lambda self, task: not task.stage_id) # Do nothing if the task has no stage + def on_task_create(self, task): + """Do stuff after the task has been created""" +``` diff --git a/edi_project_oca/static/description/index.html b/edi_project_oca/static/description/index.html new file mode 100644 index 000000000..129861429 --- /dev/null +++ b/edi_project_oca/static/description/index.html @@ -0,0 +1,478 @@ + + + + + +Edi Project + + + +
+

Edi Project

+ + +

Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

+

This module intends to create a base to be extended by local EDI rules +for project management.

+

In order to add customizations for projects, create a listener:

+
+from odoo.addons.component.core import Component
+
+
+class ProjectEventListenerExample(Component):
+    _name = "project.project.event.listener.example"
+    _inherit = "base.event.listener"
+    _apply_on = ["project.project"]
+
+    def on_project_create(self, project, vals: dict):
+        """Do stuff after the project has been created"""
+
+    def on_project_write(self, project, vals: dict):
+        """Do stuff after the project has been updated"""
+
+    def on_project_unlink(self, project):
+        """Do stuff before the project gets deleted"""
+
+

In order to add customizations for tasks, create a listener:

+
+from odoo.addons.component.core import Component
+
+
+class ProjectTaskEventListenerExample(Component):
+    _name = "project.task.event.listener.example"
+    _inherit = "base.event.listener"
+    _apply_on = ["project.task"]
+
+    def on_task_create(self, task, vals: dict):
+      """Do stuff after the task has been created"""
+
+    def on_task_write(self, task, vals: dict):
+      """Do stuff after the task has been updated"""
+
+    def on_task_unlink(self, task):
+      """Do stuff before the task gets deleted"""
+
+

Use @skip_if() decorator to avoid triggering a listener’s method if +necessary:

+
+from odoo.addons.component.core import Component
+from odoo.addons.component_event import skip_if
+
+
+class ProjectTaskEventListenerExample(Component):
+    _name = "project.task.event.listener.example"
+    _inherit = "base.event.listener"
+    _apply_on = ["project.task"]
+
+    @skip_if(lambda self, task: not task.stage_id)  # Do nothing if the task has no stage
+    def on_task_create(self, task):
+        """Do stuff after the task has been created"""
+
+

Table of contents

+ +
+

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

+
    +
  • Camptocamp
  • +
+
+
+

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

+

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

+
+
+
+ + diff --git a/edi_project_oca/tests/__init__.py b/edi_project_oca/tests/__init__.py new file mode 100644 index 000000000..3cc4cdec6 --- /dev/null +++ b/edi_project_oca/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_project_listener +from . import test_project_task_listener diff --git a/edi_project_oca/tests/common.py b/edi_project_oca/tests/common.py new file mode 100644 index 000000000..92ed61ef1 --- /dev/null +++ b/edi_project_oca/tests/common.py @@ -0,0 +1,33 @@ +# Copyright 2024 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from collections.abc import Iterable + +from odoo.addons.component.core import AbstractComponent, MetaComponent +from odoo.addons.component.tests.common import TransactionComponentRegistryCase + + +class TestEdiProjectOcaCommon(TransactionComponentRegistryCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._setup_registry(cls) + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + + @classmethod + def _build_and_add_component(cls, component_cls): + assert isinstance(component_cls, MetaComponent) + assert issubclass(component_cls, AbstractComponent) + component_cls._build_component(cls.comp_registry) + cls.comp_registry._cache.clear() + + def _check_msg_in_log_output(self, msg: str, output: Iterable): + # We use ``assertIn`` because of possible overrides of the methods we're logging + # in these tests + # EG: we want to log both creation and update of ``project.task`` via events, + # but when creating a task, the ``create()`` method is overridden to trigger the + # ``write()`` method, which in turn triggers its logging event *before* the + # ``create()`` triggers its own logging event => we end up having 2 messages in + # the logger output, and the ``create()`` log message which we're trying to + # track is the second one, not the first one. + self.assertIn(msg, output) diff --git a/edi_project_oca/tests/test_project_listener.py b/edi_project_oca/tests/test_project_listener.py new file mode 100644 index 000000000..af909c6f9 --- /dev/null +++ b/edi_project_oca/tests/test_project_listener.py @@ -0,0 +1,63 @@ +# Copyright 2024 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import logging + +from odoo.addons.component.core import Component + +from .common import TestEdiProjectOcaCommon + +_logger = logging.getLogger(__name__) + + +class TestProjectListener(TestEdiProjectOcaCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + + class ProjectEventListenerTest(Component): + _name = "project.project.event.listener.test" + _inherit = "base.event.listener" + _apply_on = ["project.project"] + + def on_project_create(self, project, vals: dict): + _logger.info(f"Created project {project.id} with values {vals}") + + def on_project_write(self, project, vals: dict): + _logger.info(f"Updated project {project.id} with values {vals}") + + def on_project_unlink(self, project): + _logger.info(f"Deleting project {project.id}") + + cls._build_and_add_component(ProjectEventListenerTest) + + def test_01_project_create(self): + vals = {"name": "Project"} + with self.assertLogs() as log_capturer: + project = self.env["project.project"].create(vals) + self._check_msg_in_log_output( + f"INFO:odoo.addons.edi_project_oca.tests.test_project_listener:" + f"Created project {project.id} with values {vals}", + log_capturer.output, + ) + + def test_02_project_write(self): + project = self.env["project.project"].create({"name": "Project"}) + vals = {"name": "Project Test"} + with self.assertLogs() as log_capturer: + project.write(vals) + self._check_msg_in_log_output( + f"INFO:odoo.addons.edi_project_oca.tests.test_project_listener:" + f"Updated project {project.id} with values {vals}", + log_capturer.output, + ) + + def test_03_project_unlink(self): + project = self.env["project.project"].create({"name": "Project"}) + with self.assertLogs() as log_capturer: + project.unlink() + self._check_msg_in_log_output( + f"INFO:odoo.addons.edi_project_oca.tests.test_project_listener:" + f"Deleting project {project.id}", + log_capturer.output, + ) diff --git a/edi_project_oca/tests/test_project_task_listener.py b/edi_project_oca/tests/test_project_task_listener.py new file mode 100644 index 000000000..c5e072545 --- /dev/null +++ b/edi_project_oca/tests/test_project_task_listener.py @@ -0,0 +1,68 @@ +# Copyright 2024 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import logging + +from odoo.addons.component.core import Component + +from .common import TestEdiProjectOcaCommon + +_logger = logging.getLogger(__name__) + + +class TestProjectTaskListener(TestEdiProjectOcaCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + + class ProjectTaskEventListenerTest(Component): + _name = "project.task.event.listener.test" + _inherit = "base.event.listener" + _apply_on = ["project.task"] + + def on_task_create(self, task, vals: dict): + _logger.info(f"Created task {task.id} with values {vals}") + + def on_task_write(self, task, vals: dict): + _logger.info(f"Updated task {task.id} with values {vals}") + + def on_task_unlink(self, task): + _logger.info(f"Deleting task {task.id}") + + cls._build_and_add_component(ProjectTaskEventListenerTest) + cls.project = cls.env["project.project"].create({"name": "Project"}) + + def test_01_project_create(self): + vals = {"name": "Task", "project_id": self.project.id} + with self.assertLogs() as log_capturer: + task = self.env["project.task"].create(vals) + self._check_msg_in_log_output( + f"INFO:odoo.addons.edi_project_oca.tests.test_project_task_listener:" + f"Created task {task.id} with values {vals}", + log_capturer.output, + ) + + def test_02_project_write(self): + task = self.env["project.task"].create( + {"name": "Task", "project_id": self.project.id} + ) + vals = {"name": "Task Test"} + with self.assertLogs() as log_capturer: + task.write(vals) + self._check_msg_in_log_output( + f"INFO:odoo.addons.edi_project_oca.tests.test_project_task_listener:" + f"Updated task {task.id} with values {vals}", + log_capturer.output, + ) + + def test_03_project_unlink(self): + task = self.env["project.task"].create( + {"name": "Task", "project_id": self.project.id} + ) + with self.assertLogs() as log_capturer: + task.unlink() + self._check_msg_in_log_output( + f"INFO:odoo.addons.edi_project_oca.tests.test_project_task_listener:" + f"Deleting task {task.id}", + log_capturer.output, + ) diff --git a/edi_project_oca/views/edi_exchange_record.xml b/edi_project_oca/views/edi_exchange_record.xml new file mode 100644 index 000000000..909a8e209 --- /dev/null +++ b/edi_project_oca/views/edi_exchange_record.xml @@ -0,0 +1,45 @@ + + + + + + Projects Exchange Record + ir.actions.act_window + edi.exchange.record + tree,form + [('model', '=', 'project.project')] + {} + + + Tasks Exchange Record + ir.actions.act_window + edi.exchange.record + tree,form + [('model', '=', 'project.task')] + {} + + + + + + + diff --git a/edi_project_oca/views/project_project.xml b/edi_project_oca/views/project_project.xml new file mode 100644 index 000000000..aa2b5381c --- /dev/null +++ b/edi_project_oca/views/project_project.xml @@ -0,0 +1,61 @@ + + + + + project.project + + + + + + + + + + + + project.project + + + + + + + +
+ + + +
+
+
+
+
+ +
diff --git a/edi_project_oca/views/project_task.xml b/edi_project_oca/views/project_task.xml new file mode 100644 index 000000000..b9013d4f9 --- /dev/null +++ b/edi_project_oca/views/project_task.xml @@ -0,0 +1,42 @@ + + + + + project.task + + + + + + + + + + project.task + + + + + + + + + + + + + +