Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions project_merge/README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

==================
Project Task Merge
==================
Expand All @@ -17,7 +13,7 @@ Project Task Merge
.. |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/license-AGPL--3-blue.png
.. |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
Expand All @@ -36,6 +32,9 @@ This module adds a wizard to merge project tasks.

A wizard that can be called from tree view of project task.

Messages, activities and attachments from the merged tasks are also
moved to the destination task.

**Table of contents**

.. contents::
Expand All @@ -48,6 +47,12 @@ To use this module, you need to:

1. Merge Project Task

Known issues / Roadmap
======================

Task dependencies (Blocked by / Blocking, ``depend_on_ids`` /
``dependent_ids``) are not transferred to the destination task on merge.

Bug Tracker
===========

Expand All @@ -70,6 +75,7 @@ Contributors
------------

- `Onestein <http://www.onestein.eu>`__
- Cyril VINH-TUNG cyril@invitu.com

Maintainers
-----------
Expand Down
1 change: 1 addition & 0 deletions project_merge/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
- [Onestein](http://www.onestein.eu)
- Cyril VINH-TUNG <cyril@invitu.com>
3 changes: 3 additions & 0 deletions project_merge/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
This module adds a wizard to merge project tasks.

A wizard that can be called from tree view of project task.

Messages, activities and attachments from the merged tasks are also moved
to the destination task.
2 changes: 2 additions & 0 deletions project_merge/readme/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Task dependencies (Blocked by / Blocking, `depend_on_ids` / `dependent_ids`)
are not transferred to the destination task on merge.
45 changes: 24 additions & 21 deletions project_merge/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<title>Project Task Merge</title>
<style type="text/css">

/*
Expand Down Expand Up @@ -360,67 +360,71 @@
</style>
</head>
<body>
<div class="document">
<div class="document" id="project-task-merge">
<h1 class="title">Project Task Merge</h1>


<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="project-task-merge">
<h1>Project Task Merge</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:30b988692949c43c5df55046d8bc453864e390e181efcf04191bc289601c4c29
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/project/tree/18.0/project_merge"><img alt="OCA/project" src="https://img.shields.io/badge/github-OCA%2Fproject-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/project-18-0/project-18-0-project_merge"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/project&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/project/tree/18.0/project_merge"><img alt="OCA/project" src="https://img.shields.io/badge/github-OCA%2Fproject-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/project-18-0/project-18-0-project_merge"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/project&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module adds a wizard to merge project tasks.</p>
<p>A wizard that can be called from tree view of project task.</p>
<p>Messages, activities and attachments from the merged tasks are also
moved to the destination task.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>To use this module, you need to:</p>
<ol class="arabic simple">
<li>Merge Project Task</li>
</ol>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
<p>Task dependencies (Blocked by / Blocking, <tt class="docutils literal">depend_on_ids</tt> /
<tt class="docutils literal">dependent_ids</tt>) are not transferred to the destination task on merge.</p>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/project/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/project/issues/new?body=module:%20project_merge%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-3">Credits</a></h2>
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-4">Authors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
<ul class="simple">
<li>Onestein</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<ul class="simple">
<li><a class="reference external" href="http://www.onestein.eu">Onestein</a></li>
<li>Cyril VINH-TUNG <a class="reference external" href="mailto:cyril&#64;invitu.com">cyril&#64;invitu.com</a></li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
Expand All @@ -433,6 +437,5 @@ <h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
</div>
</div>
</div>
</div>
</body>
</html>
52 changes: 52 additions & 0 deletions project_merge/tests/test_project_task_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,55 @@ def test_create_new_task_on_merge(self):
len(self.task_merge_2.dst_task_id._get_all_subtasks()),
"Should have 18 subtasks",
)

def test_merge_moves_messages_and_activities(self):
"""messages, activities and attachments of the merged task are
moved to the destination task, with a mention of their origin
task"""
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}
)
attachment_direct = self.env["ir.attachment"].create(
{
"name": "doc.txt",
"res_model": "project.task",
"res_id": task_B.id,
"raw": b"some content",
}
)
attachment_message = self.env["ir.attachment"].create(
{
"name": "message_doc.txt",
"res_model": "project.task",
"res_id": task_B.id,
"raw": b"some content",
}
)
message_B = task_B.message_post(
body="Some note on task B",
subject="B note",
message_type="comment",
subtype_xmlid="mail.mt_note",
attachment_ids=[attachment_message.id],
)
activity_B = task_B.activity_schedule(
"mail.mail_activity_data_todo", summary="Follow up on task B"
)

task_merge = (
self.env["project.task.merge"]
.with_context(active_ids=[task_A.id, task_B.id])
.create({})
)
task_merge.merge_tasks()

self.assertEqual(message_B.res_id, task_merge.dst_task_id.id)
self.assertEqual(message_B.subject, "From Task B: B note")
self.assertEqual(activity_B.res_id, task_merge.dst_task_id.id)
self.assertEqual(attachment_direct.res_id, task_merge.dst_task_id.id)
self.assertEqual(attachment_direct.name, "doc.txt (from Task B)")
self.assertEqual(attachment_message.res_id, task_merge.dst_task_id.id)
self.assertEqual(attachment_message.name, "message_doc.txt (from Task B)")
52 changes: 50 additions & 2 deletions project_merge/wizard/project_task_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@ def default_get(self, fields):

def merge_tasks(self):
tag_ids = self.task_ids.mapped("tag_ids").ids
attachment_ids = self.task_ids.mapped("attachment_ids").ids
values = {
"description": self._get_merge_description(),
"tag_ids": [(4, tag_id) for tag_id in tag_ids],
"attachment_ids": [(4, attachment_id) for attachment_id in attachment_ids],
"user_ids": self.user_ids.ids,
}
if self.create_new_task:
Expand All @@ -56,6 +54,8 @@ def merge_tasks(self):
self.dst_task_id.write(values)
merged_tasks = self.task_ids - self.dst_task_id
self._subscribe_merged_followers(merged_tasks)
self._merge_dependences_history(merged_tasks)
self._merge_dependences_attachments(merged_tasks)
for task in merged_tasks:
self._add_message("to", self.dst_task_id.name, task)
if task.child_ids:
Expand Down Expand Up @@ -85,6 +85,54 @@ def _subscribe_merged_followers(self, merged_tasks):
partner_ids=(merged_tasks).mapped("message_partner_ids").ids
)

def _merge_dependences_history(self, merged_tasks):
"""Move messages and activities from merged tasks to the destination
task, keeping track of their origin.
:param merged_tasks : recordset of tasks about to be archived
"""
for task_su in merged_tasks.sudo():
for message_su in task_su.message_ids:
if message_su.subject:
subject = _(
"From %(source_name)s: %(source_subject)s",
source_name=task_su.name,
source_subject=message_su.subject,
)
else:
subject = _("From %(source_name)s", source_name=task_su.name)
message_su.write({"res_id": self.dst_task_id.id, "subject": subject})
merged_tasks.sudo().activity_ids.write({"res_id": self.dst_task_id.id})

def _merge_dependences_attachments(self, merged_tasks):
"""Move attachments of the merged tasks to the destination task, and
rename them to keep track of their origin.
ir.attachment is reparented directly: project.task.attachment_ids
is a non-stored compute field, it can't be written through values.
:param merged_tasks : recordset of tasks about to be archived
"""
all_attachments = (
self.env["ir.attachment"]
.sudo()
.search(
[
("res_model", "=", "project.task"),
("res_id", "in", merged_tasks.ids),
]
)
)
task_name_per_id = {task.id: task.name for task in merged_tasks}
for attachment in all_attachments:
attachment.write(
{
"res_id": self.dst_task_id.id,
"name": _(
"%(attach_name)s (from %(task_name)s)",
attach_name=attachment.name,
task_name=task_name_per_id[attachment.res_id][:20],
),
}
)

def _add_message(self, way, task_names, task):
"""Send a message post with to advise the project task about the merge.
:param way : choice between "from" or "to"
Expand Down
Loading