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 + + +