diff --git a/project_merge_timesheet/README.rst b/project_merge_timesheet/README.rst new file mode 100644 index 0000000000..db910b99a6 --- /dev/null +++ b/project_merge_timesheet/README.rst @@ -0,0 +1,78 @@ +============================== +Project Task Merge - Timesheet +============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:32187f26ee3adbfdd19b7f76d9646622e45e2b93b69b59b3dac9309485a293c8 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fproject-lightgray.png?logo=github + :target: https://github.com/OCA/project/tree/18.0/project_merge_timesheet + :alt: OCA/project +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/project-18-0/project-18-0-project_merge_timesheet + :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/project&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the Project Task Merge wizard to also move timesheet +entries (``account.analytic.line``) from the merged tasks to the +destination task. + +**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 +------- + +* INVITU + +Contributors +------------ + +- Cyril VINH-TUNG cyril@invitu.com + +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/project `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/project_merge_timesheet/__init__.py b/project_merge_timesheet/__init__.py new file mode 100644 index 0000000000..40272379f7 --- /dev/null +++ b/project_merge_timesheet/__init__.py @@ -0,0 +1 @@ +from . import wizard diff --git a/project_merge_timesheet/__manifest__.py b/project_merge_timesheet/__manifest__.py new file mode 100644 index 0000000000..044eb9a620 --- /dev/null +++ b/project_merge_timesheet/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2026 Cyril VINH-TUNG (INVITU) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Project Task Merge - Timesheet", + "summary": "Merge timesheets when merging project tasks", + "version": "18.0.1.0.0", + "author": "INVITU, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/project", + "license": "AGPL-3", + "category": "Services/Project", + "depends": ["project_merge", "hr_timesheet"], + "auto_install": True, + "installable": True, +} diff --git a/project_merge_timesheet/pyproject.toml b/project_merge_timesheet/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/project_merge_timesheet/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/project_merge_timesheet/readme/CONTRIBUTORS.md b/project_merge_timesheet/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..6ef83798bf --- /dev/null +++ b/project_merge_timesheet/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Cyril VINH-TUNG diff --git a/project_merge_timesheet/readme/DESCRIPTION.md b/project_merge_timesheet/readme/DESCRIPTION.md new file mode 100644 index 0000000000..4d10f3d753 --- /dev/null +++ b/project_merge_timesheet/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module extends the Project Task Merge wizard to also move timesheet +entries (`account.analytic.line`) from the merged tasks to the destination +task. diff --git a/project_merge_timesheet/static/description/index.html b/project_merge_timesheet/static/description/index.html new file mode 100644 index 0000000000..6d343b7e60 --- /dev/null +++ b/project_merge_timesheet/static/description/index.html @@ -0,0 +1,425 @@ + + + + + +Project Task Merge - Timesheet + + + +
+

Project Task Merge - Timesheet

+ + +

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

+

This module extends the Project Task Merge wizard to also move timesheet +entries (account.analytic.line) from the merged tasks to the +destination task.

+

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

+
    +
  • INVITU
  • +
+
+
+

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

+

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

+
+
+
+ + diff --git a/project_merge_timesheet/tests/__init__.py b/project_merge_timesheet/tests/__init__.py new file mode 100644 index 0000000000..db5b1c8edb --- /dev/null +++ b/project_merge_timesheet/tests/__init__.py @@ -0,0 +1 @@ +from . import test_project_task_merge_timesheet diff --git a/project_merge_timesheet/tests/test_project_task_merge_timesheet.py b/project_merge_timesheet/tests/test_project_task_merge_timesheet.py new file mode 100644 index 0000000000..115621495e --- /dev/null +++ b/project_merge_timesheet/tests/test_project_task_merge_timesheet.py @@ -0,0 +1,61 @@ +# Copyright 2026 Cyril VINH-TUNG (INVITU) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests import tagged + +from odoo.addons.project.tests.test_project_base import TestProjectCommon + + +@tagged("-at_install", "post_install") +class TestProjectMergeTimesheet(TestProjectCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.employee = cls.env["hr.employee"].create( + {"name": "Test Employee", "employee_type": "freelance"} + ) + + def test_merge_moves_timesheets(self): + """timesheet lines of the merged task are moved to the destination + task, and their project follows automatically""" + task_A = self.env["project.task"].create( + {"name": "Task A", "project_id": self.project_goats.id} + ) + task_B = self.env["project.task"].create( + {"name": "Task B", "project_id": self.project_goats.id} + ) + timesheet_B = self.env["account.analytic.line"].create( + { + "name": "Work on task B", + "project_id": task_B.project_id.id, + "task_id": task_B.id, + "employee_id": self.employee.id, + "unit_amount": 2.5, + } + ) + + task_merge = ( + self.env["project.task.merge"] + .with_context(active_ids=[task_A.id, task_B.id]) + .create({}) + ) + task_merge.merge_tasks() + + self.assertEqual(timesheet_B.task_id, task_merge.dst_task_id) + self.assertEqual(timesheet_B.project_id, task_merge.dst_task_id.project_id) + + def test_merge_without_timesheets_on_private_task(self): + """merging tasks with no timesheets into a private (project-less) + task must not raise, even though such tasks cannot receive + timesheets""" + task_private_A = self.env["project.task"].create({"name": "Private A"}) + task_private_B = self.env["project.task"].create({"name": "Private B"}) + + task_merge = ( + self.env["project.task.merge"] + .with_context(active_ids=[task_private_A.id, task_private_B.id]) + .create({}) + ) + task_merge.merge_tasks() + + self.assertFalse(task_merge.dst_task_id.project_id) diff --git a/project_merge_timesheet/wizard/__init__.py b/project_merge_timesheet/wizard/__init__.py new file mode 100644 index 0000000000..b201c468b8 --- /dev/null +++ b/project_merge_timesheet/wizard/__init__.py @@ -0,0 +1 @@ +from . import project_task_merge diff --git a/project_merge_timesheet/wizard/project_task_merge.py b/project_merge_timesheet/wizard/project_task_merge.py new file mode 100644 index 0000000000..42b8922884 --- /dev/null +++ b/project_merge_timesheet/wizard/project_task_merge.py @@ -0,0 +1,20 @@ +# Copyright 2026 Cyril VINH-TUNG (INVITU) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class ProjectTaskMerge(models.TransientModel): + _inherit = "project.task.merge" + + def merge_tasks(self): + original_task_ids = self.task_ids + result = super().merge_tasks() + merged_tasks = original_task_ids - self.dst_task_id + self._merge_dependences_timesheets(merged_tasks) + return result + + def _merge_dependences_timesheets(self, merged_tasks): + timesheet_ids = merged_tasks.sudo().timesheet_ids + if timesheet_ids: + timesheet_ids.write({"task_id": self.dst_task_id.id})