Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TA#72203 [MIG][16.0] project_task_subtask_same_project #458

Open
wants to merge 7 commits into
base: 16.0
Choose a base branch
from
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
3 changes: 2 additions & 1 deletion .docker_files/main/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@
"project_task_parent_domain",
"project_task_reference",
"project_task_resource_type",
"project_time_range",
"project_task_search_parent_subtask",
"project_task_stage_external_mail",
"project_task_subtask_same_project",
"project_time_range",
"project_track_end_date",
"project_type_advanced",
],
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ COPY project_task_reference /mnt/extra-addons/project_task_reference
COPY project_task_resource_type /mnt/extra-addons/project_task_resource_type
COPY project_task_stage_external_mail /mnt/extra-addons/project_task_stage_external_mail
COPY project_task_search_parent_subtask /mnt/extra-addons/project_task_search_parent_subtask
COPY project_task_subtask_same_project /mnt/extra-addons/project_task_subtask_same_project
COPY project_time_range /mnt/extra-addons/project_time_range
COPY project_track_end_date /mnt/extra-addons/project_track_end_date
COPY project_type_advanced /mnt/extra-addons/project_type_advanced
Expand Down
27 changes: 27 additions & 0 deletions project_task_subtask_same_project/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Project Task Subtask Same Project
=================================
Ensure that subtasks always belong to the same project as their parent task.

Starting from **Odoo v16**, a new feature allows subtasks to belong to one project
while being displayed under a different project using the `display_project_id` field.

This module **disables this behavior**, ensuring that tasks and their subtasks
are always under the same project **both in data and display**.

- **Subtasks remain in the same project as their parent task.**
- When attempting to assign a subtask to a task from a different project,
an **error message** is displayed.

.. image:: static/description/subtask_in_different_project.png


- In the subtask form view, the **display_project** field is **readonly**.
- The value is automatically propagated from the parent task.
- When changing the **project** on a parent task, both the `project_id`
and `display_project_id` fields are automatically updated on all its subtasks.

.. image:: static/description/subtask_project_readonly.png

Contributors
------------
* Numigi (tm) and all its contributors (https://bit.ly/numigiens)
4 changes: 4 additions & 0 deletions project_task_subtask_same_project/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2025 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import models

Check notice on line 4 in project_task_subtask_same_project/__init__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_task_subtask_same_project/__init__.py#L4

'.models' imported but unused (F401)
19 changes: 19 additions & 0 deletions project_task_subtask_same_project/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2025 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

{

Check warning on line 4 in project_task_subtask_same_project/__manifest__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_task_subtask_same_project/__manifest__.py#L4

Statement seems to have no effect
"name": "Project Task Subtask Same Project",
"version": "16.0.1.0.0",
"author": "Numigi",
"maintainer": "Numigi",
"website": "https://bit.ly/numigi-com",
"license": "LGPL-3",
"category": "Project",
"summary": "Constrain a subtask to be on the same project that its parent",
"depends": ["project"],
"data": [
"data/res_config_data.xml",
"views/project_task_views.xml",
],
"installable": True,
}
12 changes: 12 additions & 0 deletions project_task_subtask_same_project/data/res_config_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record model="res.config.settings" id="res_config_settings">
<field name="group_subtask_project" eval="1" />
</record>

<function model="res.config.settings" name="execute">
<value eval="[ref('res_config_settings')]" />
</function>
</data>
</odoo>
32 changes: 32 additions & 0 deletions project_task_subtask_same_project/i18n/fr.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_task_subtask_same_project
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-06 06:00+0000\n"
"PO-Revision-Date: 2025-02-06 06:00+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: project_task_subtask_same_project
#: model:ir.model,name:project_task_subtask_same_project.model_project_task
msgid "Task"
msgstr "Tâche"

#. module: project_task_subtask_same_project
#. odoo-python
#: code:addons/project_task_subtask_same_project/models/project_task.py:0
#, python-format
msgid ""
"The subtask '{subtask}' must be in the same project as its parent task "
"'{parent_task}'."
msgstr ""
"La sous-tâche {subtask} doit être sous le même projet que sa tâche parente "
"'{parent_task}'."
4 changes: 4 additions & 0 deletions project_task_subtask_same_project/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2025 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import project_task

Check notice on line 4 in project_task_subtask_same_project/models/__init__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_task_subtask_same_project/models/__init__.py#L4

'.project_task' imported but unused (F401)
39 changes: 39 additions & 0 deletions project_task_subtask_same_project/models/project_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2025 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo import _, api, models
from odoo.exceptions import ValidationError


class ProjectTask(models.Model):
_inherit = "project.task"

def write(self, vals):
"""
Propagate the value of the project to the subtask when it
is changed on the parent task.
"""
res = super().write(vals)
for task in self:
if task.child_ids and "project_id" in vals:
task.child_ids.write(
{
"project_id": vals["project_id"],
"display_project_id": vals["project_id"],
}
)
return res

@api.constrains("project_id", "parent_id", "display_project_id")
def _check_subtask_project_consistency(self):
for task in self:
if task.parent_id and task.project_id != task.parent_id.project_id:
raise ValidationError(
_(
"The subtask '{subtask}' must be in the same project"
"as its parent task '{parent_task}'."
).format(
subtask=task.display_name,
parent_task=task.parent_id.display_name,
)
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions project_task_subtask_same_project/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2025 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import test_project_task
57 changes: 57 additions & 0 deletions project_task_subtask_same_project/tests/test_project_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright 2025 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import pytest
from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase


class TestProjectTaskSubTaskSameProject(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.project_a = cls.env["project.project"].create({"name": "projectA"})
cls.project_b = cls.env["project.project"].create({"name": "projectB"})
cls.task_parent = cls.env["project.task"].create(
{"name": "Task Parent", "project_id": cls.project_a.id}
)
cls.subtask_1 = cls.env["project.task"].create(
{
"name": "Task Child 1",
"project_id": cls.task_parent.project_id.id,
"parent_id": cls.task_parent.id,
"planned_hours": 1.0,
}
)
cls.subtask_2 = cls.env["project.task"].create(
{
"name": "Task Child 2",
"project_id": cls.task_parent.project_id.id,
"parent_id": cls.task_parent.id,
"planned_hours": 1.0,
}
)
cls.subtask_3 = cls.env["project.task"].create(
{
"name": "Task Child 2",
"project_id": cls.task_parent.project_id.id,
"parent_id": cls.subtask_2.id,
"planned_hours": 1.0,
}
)

def test_whenParentTaskChangeProject_thenSubTaskInheritNewProject(self):
self.task_parent.project_id = self.project_b.id

assert self.subtask_1.project_id == self.project_b
assert self.subtask_1.display_project_id == self.project_b

assert self.subtask_2.project_id == self.project_b
assert self.subtask_2.display_project_id == self.project_b

assert self.subtask_3.project_id == self.project_b
assert self.subtask_3.display_project_id == self.project_b

def test_onUpdateSubtask_ifNotSameProject_raiseError(self):
with pytest.raises(ValidationError):
self.subtask_1.project_id = self.project_b
29 changes: 29 additions & 0 deletions project_task_subtask_same_project/views/project_task_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>

<record id="view_task_form2" model="ir.ui.view">
<field name="name">Project task: Project Readonly for Subtasks</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml">
<field name="display_project_id" position="attributes">
<attribute name="attrs">{'readonly': [('parent_id', '!=', False)],'invisible': [('parent_id', '=', False)]}</attribute>
</field>
</field>
</record>

<record id="view_task_kanban" model="ir.ui.view">
<field name="name">Project task: Project Readonly for Subtasks Kanban</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_kanban"/>
<field name="arch" type="xml">
<field name="project_id" position="after">
<field name="parent_id" invisible="1"/>
</field>
<field name="project_id" position="attributes">
<attribute name="attrs">{'readonly': [('parent_id', '!=', False)]}</attribute>
</field>
</field>
</record>

</odoo>
Loading