diff --git a/project_ux/__manifest__.py b/project_ux/__manifest__.py
index 5b302ebc..acb899d3 100644
--- a/project_ux/__manifest__.py
+++ b/project_ux/__manifest__.py
@@ -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",
diff --git a/project_ux/models/__init__.py b/project_ux/models/__init__.py
index c6141a05..cc9f3090 100644
--- a/project_ux/models/__init__.py
+++ b/project_ux/models/__init__.py
@@ -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
diff --git a/project_ux/models/project_project.py b/project_ux/models/project_project.py
new file mode 100644
index 00000000..ec0a0a8a
--- /dev/null
+++ b/project_ux/models/project_project.py
@@ -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",
+ )
diff --git a/project_ux/models/project_task.py b/project_ux/models/project_task.py
index adc79fcc..7b44724d 100644
--- a/project_ux/models/project_task.py
+++ b/project_ux/models/project_task.py
@@ -3,6 +3,7 @@
# directory
##############################################################################
from odoo import api, fields, models
+from odoo.fields import Domain
CLOSED_STATES = {
"1_done": "Done",
@@ -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")
@@ -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,
@@ -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)
diff --git a/project_ux/tests/test_project_task.py b/project_ux/tests/test_project_task.py
index 0fc991b3..be4e4477 100644
--- a/project_ux/tests/test_project_task.py
+++ b/project_ux/tests/test_project_task.py
@@ -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"]
@@ -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",
}
@@ -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)
diff --git a/project_ux/views/project_project_views.xml b/project_ux/views/project_project_views.xml
index 37db82a9..8bbc9cac 100644
--- a/project_ux/views/project_project_views.xml
+++ b/project_ux/views/project_project_views.xml
@@ -52,6 +52,11 @@
project.group_project_user
+
+
+
+
+
diff --git a/project_ux/views/project_task_views.xml b/project_ux/views/project_task_views.xml
index 9408ed00..e966989d 100644
--- a/project_ux/views/project_task_views.xml
+++ b/project_ux/views/project_task_views.xml
@@ -6,9 +6,17 @@
project.task
+
+
+
+
+
+ #
+
+
many2many
@@ -33,6 +41,9 @@
project.task
+
+
+