From 3b661f678411d3d607f54de1f6dab2cf341433b4 Mon Sep 17 00:00:00 2001 From: sngohodoo Date: Fri, 19 Jun 2026 15:57:07 +0530 Subject: [PATCH 1/2] [ADD] task_manager: added new module for task management Managing tasks can become very hard and complicated. This module add a basic task tracking and management with automated tools to speed up creation and cleanup, - Add button to quickly archive all the task with the done status - Automatically add tag as anyone get's assigned to the task and vice versa - Can create multiple task with just a single click. --- task_manager/__init__.py | 1 + task_manager/__manifest__.py | 15 +++ task_manager/data/ir_cron_data.xml | 20 ++++ task_manager/models/__init__.py | 2 + task_manager/models/task_manager.py | 103 ++++++++++++++++++ task_manager/models/task_manager_tags.py | 15 +++ task_manager/security/ir.access.csv | 3 + task_manager/views/task_manager_menus.xml | 13 +++ task_manager/views/task_manager_tags_view.xml | 37 +++++++ task_manager/views/task_manager_view.xml | 85 +++++++++++++++ 10 files changed, 294 insertions(+) create mode 100644 task_manager/__init__.py create mode 100644 task_manager/__manifest__.py create mode 100644 task_manager/data/ir_cron_data.xml create mode 100644 task_manager/models/__init__.py create mode 100644 task_manager/models/task_manager.py create mode 100644 task_manager/models/task_manager_tags.py create mode 100644 task_manager/security/ir.access.csv create mode 100644 task_manager/views/task_manager_menus.xml create mode 100644 task_manager/views/task_manager_tags_view.xml create mode 100644 task_manager/views/task_manager_view.xml diff --git a/task_manager/__init__.py b/task_manager/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/task_manager/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/task_manager/__manifest__.py b/task_manager/__manifest__.py new file mode 100644 index 00000000000..79184c18a67 --- /dev/null +++ b/task_manager/__manifest__.py @@ -0,0 +1,15 @@ +{ + "name": "Task Manager", + "application": True, + "installable": True, + "author": "sngoh", + "depends": ["mail"], + "license": "LGPL-3", + "data": [ + "security/ir.access.csv", + "data/ir_cron_data.xml", + "views/task_manager_tags_view.xml", + "views/task_manager_view.xml", + "views/task_manager_menus.xml", + ], +} diff --git a/task_manager/data/ir_cron_data.xml b/task_manager/data/ir_cron_data.xml new file mode 100644 index 00000000000..f56eb37fa09 --- /dev/null +++ b/task_manager/data/ir_cron_data.xml @@ -0,0 +1,20 @@ + + + + + + + Auto Archive Task After 7pm + + code + model._auto_archive() + 1 + days + + + + + + + + diff --git a/task_manager/models/__init__.py b/task_manager/models/__init__.py new file mode 100644 index 00000000000..21ceadcea5e --- /dev/null +++ b/task_manager/models/__init__.py @@ -0,0 +1,2 @@ +from . import task_manager +from . import task_manager_tags diff --git a/task_manager/models/task_manager.py b/task_manager/models/task_manager.py new file mode 100644 index 00000000000..b4d28dd6c78 --- /dev/null +++ b/task_manager/models/task_manager.py @@ -0,0 +1,103 @@ +from odoo import api, Command, fields, models +from odoo.exceptions import UserError + + +class TaskManagerTask(models.Model): + _name = "task.manager" + _inherit = ["mail.thread"] + _description = "Task Manager" + + name = fields.Char(tracking=True, required=True) + active = fields.Boolean(default=True) + assigned_user = fields.Many2one("res.users", tracking=True) + deadline = fields.Datetime(tracking=True, required=True) + status = fields.Selection( + string="Status", + selection=[ + ("new", "New"), + ("in_progress", "In Progress"), + ("done", "Done"), + ], + default="new", + tracking=True, + ) + tag_ids = fields.Many2many("task.manager.tags") + days_remaining = fields.Integer(compute="_compute_days_remaining") + count_of_assignes = fields.Integer(compute="_compute_count_of_assignes") + + @api.depends("deadline") + def _compute_days_remaining(self): + for task_manager in self: + task_manager.days_remaining = ( + task_manager.deadline - fields.Datetime.today() + ).days + + @api.depends("assigned_user") + def _compute_count_of_assignes(self): + for task_manager in self: + task_manager.count_of_assignes = len(task_manager.assigned_user) + + @api.onchange("assigned_user") + def _onchange_assigned_user(self): + tag_name = "assigned" + + for task_manager in self: + if not task_manager.assigned_user: + assigned_tag = task_manager.env["task.manager.tags"].search( + [("name", "=", tag_name)], + ) + if assigned_tag: + task_manager.tag_ids = [Command.unlink(assigned_tag.id)] + continue + if task_manager.tag_ids.filtered(lambda t: t.name == tag_name): + continue + assigned_tag = task_manager.env["task.manager.tags"].search( + [("name", "=", tag_name)] + ) + if not assigned_tag: + assigned_tag = task_manager.env["task.manager.tags"].create( + {"name": tag_name} + ) + task_manager.tag_ids = [Command.link(assigned_tag.id)] + + def write(self, vals): + is_archiving = "active" in vals + for task_manager in self: + if task_manager.status == "done" and not is_archiving: + raise UserError("Cannot update task's details in the Done state.") + + return super().write(vals) + + def quick_archive(self): + if self: + tasks = self.filtered(lambda t: t.status == "done" and t.active) + else: + tasks = self.env["task.manager"].search( + [("status", "=", "done"), ("active", "=", True)] + ) + if tasks: + tasks.action_archive() + + def print_task_count(self): + task_count = self.env["task.manager"].search_count([("active", "=", True)]) + return { + "effect": { + "fadeout": "slow", + "message": f"{task_count} Tasks are there.", + "img_url": "/web/static/img/smile.svg", + "type": "rainbow_man", + } + } + + def generate_multiple_tasks(self): + tasks = [ + {"name": "Generated Task 1", "deadline": fields.Datetime.today()}, + {"name": "Generated Task 2", "deadline": fields.Datetime.today()}, + ] + self.env["task.manager"].create(tasks) + + def _auto_archive(self): + done_tasks = self.env["task.manager"].search( + [("status", "=", "done"), ("active", "=", True)], + ) + done_tasks.action_archive() diff --git a/task_manager/models/task_manager_tags.py b/task_manager/models/task_manager_tags.py new file mode 100644 index 00000000000..814fa75c462 --- /dev/null +++ b/task_manager/models/task_manager_tags.py @@ -0,0 +1,15 @@ +from odoo import fields, models + + +class TaskManagerTags(models.Model): + _name = "task.manager.tags" + _description = "Tags for Tasks" + _order = "name" + + name = fields.Char(required=True) + color = fields.Integer() + + _check_unique_tag = models.Constraint( + "UNIQUE(name)", + "A property tag name must be unique", + ) diff --git a/task_manager/security/ir.access.csv b/task_manager/security/ir.access.csv new file mode 100644 index 00000000000..542acada176 --- /dev/null +++ b/task_manager/security/ir.access.csv @@ -0,0 +1,3 @@ +id,name,model_id,group_id/id,operation,domain +access_task_manager,access_task_manager,task.manager,base.group_user,crud, +access_task_manager_tags,access_task_manager_tags,task.manager.tags,base.group_user,crud, diff --git a/task_manager/views/task_manager_menus.xml b/task_manager/views/task_manager_menus.xml new file mode 100644 index 00000000000..545f8a7b31b --- /dev/null +++ b/task_manager/views/task_manager_menus.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/task_manager/views/task_manager_tags_view.xml b/task_manager/views/task_manager_tags_view.xml new file mode 100644 index 00000000000..f4162f560bc --- /dev/null +++ b/task_manager/views/task_manager_tags_view.xml @@ -0,0 +1,37 @@ + + + + + task.manager.tags.view.list + task.manager.tags + + + + + + + + + + task.manager.tags.view.form + task.manager.tags + +
+ +

+ +

+ + +
+
+
+
+ + + Task Tags + task.manager.tags + list,form + + +
diff --git a/task_manager/views/task_manager_view.xml b/task_manager/views/task_manager_view.xml new file mode 100644 index 00000000000..d3f2d92d731 --- /dev/null +++ b/task_manager/views/task_manager_view.xml @@ -0,0 +1,85 @@ + + + + + task.manager.list + task.manager + + +
+
+ + + + + + + +
+
+
+ + + task.manager.form + task.manager + +
+
+ +
+ +

+ +

+ + + + + + + + + + + +
+ + +
+
+ + + task.manager.view.kanban + task.manager + + + + + + + + + + + Tasks + task.manager + list,form,kanban + + +
From 38ba7ca2bf5762713f6c59d6cab661865d4dc2c2 Mon Sep 17 00:00:00 2001 From: sngohodoo Date: Mon, 22 Jun 2026 12:07:37 +0530 Subject: [PATCH 2/2] [IMP] task_manager: added some demo users and improved optimization From now whenever the user installs the module some demo users are already created by default to be assigned in so If user wants so they can assign task to them directly. Also improved the tag assigned process. Also introduce automatic garbage collector to remove the archived task which has been there for 31 days. --- task_manager/__init__.py | 45 +++++++++++++ task_manager/__manifest__.py | 3 + task_manager/models/task_manager.py | 97 +++++++++++++++++++---------- 3 files changed, 112 insertions(+), 33 deletions(-) diff --git a/task_manager/__init__.py b/task_manager/__init__.py index 0650744f6bc..1793d20a10e 100644 --- a/task_manager/__init__.py +++ b/task_manager/__init__.py @@ -1 +1,46 @@ from . import models + +from odoo import Command + + +def add_tags(env): + tags = [] + for i in range(10): + tags.append( + { + "name": f"tag_{i + 1}", + "color": i, + } + ) + + env["task.manager.tags"].create(tags) + + +def _pre_init_hook(env): + query = """ + DO $$ + DECLARE + i INT; + new_partner_id INT; + BEGIN + FOR i IN 0..9 LOOP + INSERT INTO res_partner (name, phone, company_id, active, type) + VALUES ('user_' || i, '0987654321', 1, true, 'contact') + RETURNING id INTO new_partner_id; + + INSERT INTO res_users (login, partner_id, company_id, active, notification_type) + VALUES ('user_' || i || '@example.com', new_partner_id, 1, true, 'email'); + END LOOP; + END $$; + """ + env.cr.execute(query) + + +def remove_users_with_mobile_number(env): + target_logins = [f"user_{i}@example.com" for i in range(10)] + users_to_remove = env["res.users"].search([("login", "in", target_logins)]) + + if users_to_remove: + partner_records = users_to_remove.partner_id + users_to_remove.unlink() + partner_records.unlink() diff --git a/task_manager/__manifest__.py b/task_manager/__manifest__.py index 79184c18a67..344ec6d5bdf 100644 --- a/task_manager/__manifest__.py +++ b/task_manager/__manifest__.py @@ -12,4 +12,7 @@ "views/task_manager_view.xml", "views/task_manager_menus.xml", ], + "pre_init_hook": "_pre_init_hook", + "post_init_hook": "add_tags", + "uninstall_hook": "remove_users_with_mobile_number", } diff --git a/task_manager/models/task_manager.py b/task_manager/models/task_manager.py index b4d28dd6c78..6877d280f90 100644 --- a/task_manager/models/task_manager.py +++ b/task_manager/models/task_manager.py @@ -7,10 +7,17 @@ class TaskManagerTask(models.Model): _inherit = ["mail.thread"] _description = "Task Manager" + def _get_default_deadline(self): + return fields.Datetime.add(fields.Datetime.today(), days=3) + name = fields.Char(tracking=True, required=True) active = fields.Boolean(default=True) assigned_user = fields.Many2one("res.users", tracking=True) - deadline = fields.Datetime(tracking=True, required=True) + deadline = fields.Datetime( + tracking=True, + required=True, + default=lambda self: self._get_default_deadline(), + ) status = fields.Selection( string="Status", selection=[ @@ -21,49 +28,49 @@ class TaskManagerTask(models.Model): default="new", tracking=True, ) - tag_ids = fields.Many2many("task.manager.tags") + tag_ids = fields.Many2many( + "task.manager.tags", + compute="_compute_assigned_user", + store=True, + ) days_remaining = fields.Integer(compute="_compute_days_remaining") count_of_assignes = fields.Integer(compute="_compute_count_of_assignes") @api.depends("deadline") def _compute_days_remaining(self): - for task_manager in self: - task_manager.days_remaining = ( - task_manager.deadline - fields.Datetime.today() - ).days + for task in self: + task.days_remaining = (task.deadline - fields.Datetime.today()).days @api.depends("assigned_user") def _compute_count_of_assignes(self): - for task_manager in self: - task_manager.count_of_assignes = len(task_manager.assigned_user) + for task in self: + task.count_of_assignes = len(task.assigned_user) - @api.onchange("assigned_user") - def _onchange_assigned_user(self): + @api.depends("assigned_user") + def _compute_assigned_user(self): tag_name = "assigned" + tag = self.env["task.manager.tags"].search([("name", "=", tag_name)], limit=1) + if not tag: + tag = self.env["task.manager.tags"].create({"name": tag_name}) - for task_manager in self: - if not task_manager.assigned_user: - assigned_tag = task_manager.env["task.manager.tags"].search( - [("name", "=", tag_name)], - ) - if assigned_tag: - task_manager.tag_ids = [Command.unlink(assigned_tag.id)] - continue - if task_manager.tag_ids.filtered(lambda t: t.name == tag_name): - continue - assigned_tag = task_manager.env["task.manager.tags"].search( - [("name", "=", tag_name)] - ) - if not assigned_tag: - assigned_tag = task_manager.env["task.manager.tags"].create( - {"name": tag_name} - ) - task_manager.tag_ids = [Command.link(assigned_tag.id)] + unlink_ids = [] + link_ids = [] + + for task in self: + if not task.assigned_user and tag in task.tag_ids: + unlink_ids.append(task.id) + elif task.assigned_user and tag not in task.tag_ids: + link_ids.append(task.id) + + if unlink_ids: + self.browse(unlink_ids).tag_ids = [Command.unlink(tag.id)] + if link_ids: + self.browse(link_ids).tag_ids = [Command.link(tag.id)] def write(self, vals): is_archiving = "active" in vals - for task_manager in self: - if task_manager.status == "done" and not is_archiving: + for task in self: + if task.status == "done" and not is_archiving: raise UserError("Cannot update task's details in the Done state.") return super().write(vals) @@ -73,13 +80,16 @@ def quick_archive(self): tasks = self.filtered(lambda t: t.status == "done" and t.active) else: tasks = self.env["task.manager"].search( - [("status", "=", "done"), ("active", "=", True)] + [ + ("status", "=", "done"), + ("active", "=", True), + ], ) if tasks: tasks.action_archive() def print_task_count(self): - task_count = self.env["task.manager"].search_count([("active", "=", True)]) + task_count = self.env["task.manager"].search_count([]) return { "effect": { "fadeout": "slow", @@ -98,6 +108,27 @@ def generate_multiple_tasks(self): def _auto_archive(self): done_tasks = self.env["task.manager"].search( - [("status", "=", "done"), ("active", "=", True)], + [ + ("status", "=", "done"), + ("active", "=", True), + ], ) done_tasks.action_archive() + + @api.autovacuum + def _gc_delete_archive_task(self): + old_archived_tasks = self.env["task.manager"].search( + [ + ("active", "=", False), + ( + "create_date", + "<", + fields.Datetime.subtract( + fields.Datetime.today(), + days=31, + ), + ), + ], + ) + # breakpoint() + old_archived_tasks.unlink()