Skip to content
Closed
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
2 changes: 1 addition & 1 deletion project_ux/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
##############################################################################
{
"name": "Project UX",
"version": "19.0.1.0.0",
"version": "19.0.2.0.0",
"category": "Project Management",
"sequence": 14,
"author": "ADHOC SA",
Expand Down
1 change: 1 addition & 0 deletions project_ux/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from . import project_project
from . import project_task_type
from . import project_task
13 changes: 13 additions & 0 deletions project_ux/models/project_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from odoo import fields, models


class ProjectProject(models.Model):
_inherit = "project.project"

show_task_id = fields.Boolean(
string="Show Task ID",
)
44 changes: 44 additions & 0 deletions project_ux/models/project_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# directory
##############################################################################
from odoo import api, fields, models
from odoo.fields import Domain

CLOSED_STATES = {
"1_done": "Done",
Expand All @@ -13,6 +14,29 @@
class Task(models.Model):
_inherit = "project.task"

@api.model
def _get_id_prefix_domain(self, prefix):
enabled_domain = Domain("project_id.show_task_id", "=", True)
max_task_id = self.search([("project_id.show_task_id", "=", True)], order="id desc", limit=1).id
if not max_task_id:
return Domain.FALSE

prefix_int = int(prefix)
prefix_domains = [Domain.AND([enabled_domain, Domain("id", "=", prefix_int)])]
factor = 10
while prefix_int * factor <= max_task_id:
prefix_domains.append(
Domain.AND(
[
enabled_domain,
Domain("id", ">=", prefix_int * factor),
Domain("id", "<", (prefix_int + 1) * factor),
]
)
)
factor *= 10
return Domain.OR(prefix_domains)

display_in_project = fields.Boolean(default=True)

@api.depends("project_id")
Expand All @@ -38,6 +62,7 @@ def create(self, vals_list):
vals["display_in_project"] = True
return super().create(vals_list)

show_task_id = fields.Boolean(related="project_id.show_task_id", readonly=True)
dont_send_stage_email = fields.Boolean(
string="Don't Send Stage Email",
default=False,
Expand All @@ -47,6 +72,25 @@ def create(self, vals_list):
)
is_closed = fields.Boolean(related="stage_id.fold", string="Folded in Kanban", index=True)

@api.depends("name", "project_id.show_task_id")
def _compute_display_name(self):
super()._compute_display_name()
for task in self:
if task.project_id.show_task_id and task.id and task.display_name:
task.display_name = f"{task.display_name} (#{task.id})"

@api.model
def _search_display_name(self, operator, value):
domain = super()._search_display_name(operator, value)
normalized_name = (value or "").strip()
if normalized_name.startswith("#"):
normalized_name = normalized_name[1:].strip()

if normalized_name.isdigit() and operator in ("ilike", "like", "=ilike", "=like"):
return Domain.OR([domain, self._get_id_prefix_domain(normalized_name)])

return domain

def _track_template(self, changes):
task = self[0]
res = super()._track_template(changes)
Expand Down
56 changes: 56 additions & 0 deletions project_ux/tests/test_project_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
class TestTaskModel(TransactionCase):
def setUp(self):
super(TestTaskModel, self).setUp()
self.Project = self.env["project.project"]
self.Task = self.env["project.task"]
self.Stage = self.env["project.task.type"]
self.Template = self.env["mail.template"]
Expand All @@ -18,10 +19,12 @@ def setUp(self):
)

self.test_stage = self.Stage.create({"name": "Test Stage", "mail_template_id": mail_template.id})
self.test_project = self.Project.create({"name": "Test Project"})

self.test_task = self.Task.create(
{
"name": "Test Task",
"project_id": self.test_project.id,
"stage_id": self.test_stage.id,
"state": "01_in_progress",
}
Expand All @@ -38,3 +41,56 @@ def test_compute_state_with_dependencies(self):
dependent_task.state = "1_done"
self.test_task._compute_state()
self.assertEqual(self.test_task.state, "01_in_progress")

def test_task_display_name_shows_id_when_enabled_on_project(self):
self.assertEqual(self.test_task.display_name, "Test Task")

self.test_project.show_task_id = True

self.assertEqual(self.test_task.display_name, f"Test Task (#{self.test_task.id})")

def test_task_display_name_hides_id_when_disabled_on_project(self):
self.test_project.show_task_id = True
self.assertEqual(self.test_task.display_name, f"Test Task (#{self.test_task.id})")

self.test_project.show_task_id = False

self.assertEqual(self.test_task.display_name, "Test Task")

def test_name_search_finds_task_by_plain_id_when_enabled_on_project(self):
self.test_project.show_task_id = True

result = self.Task.name_search(str(self.test_task.id), limit=1)

self.assertEqual(result, [(self.test_task.id, f"Test Task (#{self.test_task.id})")])

def test_name_search_finds_task_by_hash_prefixed_id_when_enabled_on_project(self):
self.test_project.show_task_id = True

result = self.Task.name_search(f"#{self.test_task.id}", limit=1)

self.assertEqual(result, [(self.test_task.id, f"Test Task (#{self.test_task.id})")])

def test_name_search_finds_tasks_by_id_prefix_when_enabled_on_project(self):
self.test_project.show_task_id = True

tasks = self.test_task
while tasks[-1].id < 10:
tasks |= self.Task.create(
{
"name": f"Test Task {len(tasks)}",
"project_id": self.test_project.id,
"stage_id": self.test_stage.id,
"state": "01_in_progress",
}
)

prefix = str(tasks[-1].id)[:-1]
result_ids = [task_id for task_id, __ in self.Task.name_search(prefix, limit=20)]

self.assertIn(tasks[-1].id, result_ids)

def test_name_search_does_not_find_task_by_id_when_disabled_on_project(self):
result_ids = [task_id for task_id, __ in self.Task.name_search(str(self.test_task.id), limit=20)]

self.assertNotIn(self.test_task.id, result_ids)
5 changes: 5 additions & 0 deletions project_ux/views/project_project_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
<xpath expr="//button[@name='project_update_all_action']" position="attributes">
<attribute name="groups">project.group_project_user</attribute>
</xpath>
<xpath expr="//setting[@id='project_milestone_setting']" position="after">
<setting class="col-lg-12" id="task_id_display_setting" help="Show the task ID in the task form title and breadcrumb">
<field name="show_task_id"/>
</setting>
</xpath>
</field>
</record>

Expand Down
11 changes: 11 additions & 0 deletions project_ux/views/project_task_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml">
<field name="tag_ids" position="before">
<field name="show_task_id" invisible="1"/>
</field>
<field name="tag_ids" position="after">
<field name="dont_send_stage_email" widget="boolean_toggle"/>
</field>
<xpath expr="//h1[hasclass('d-flex') and hasclass('w-100')]/field[@name='name']" position="before">
<span class="o_task_name_id text-nowrap me-2 text-muted fs-4 align-self-center" invisible="not show_task_id or not id">
#<field name="id" readonly="1" nolabel="1" class="oe_inline"/>
</span>
</xpath>
<xpath expr="//field[@name='child_ids']" position="attributes">
<attribute name="widget">many2many</attribute>
</xpath>
Expand All @@ -33,6 +41,9 @@
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_search_form"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="id" string="Task ID" filter_domain="[('id', '=', self)]"/>
</field>
<filter name="message_needaction" position="before">
<filter string="Is Task" name="is_task" domain="[('parent_id','=', False)]"/>
<filter string="Is Sub-Task" name="is_sub_task" domain="[('parent_id','!=', False)]"/>
Expand Down
Loading