diff --git a/addons_extensions/project_task_timesheet_extended/__init__.py b/addons_extensions/project_task_timesheet_extended/__init__.py new file mode 100644 index 000000000..119a556b9 --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/__init__.py @@ -0,0 +1,2 @@ +from . import models, wizards +from .hooks import post_init_hook diff --git a/addons_extensions/project_task_timesheet_extended/__manifest__.py b/addons_extensions/project_task_timesheet_extended/__manifest__.py new file mode 100644 index 000000000..95f62283d --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/__manifest__.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'Project Task Timesheet Extended', + 'version': '18.0.1.0.0', + 'category': 'Project', + 'summary': 'Enhancements and extended features for Projects, Tasks, and Timesheets', + 'description': """ +Project Task Timesheet Extended +=============================== +This module extends and enhances the functionality of Odoo's Project, Task, and Timesheet modules. + +Key Features: +-------------- +- Project - Set Team and members +- Additional tools and views for project management. +- Extended task features and custom workflows. +- Improved timesheet tracking and reporting. + """, + 'author': 'Gadi Pranay Sai Durga Kumar', + 'website': 'https://ftprotech.in', # change if you have another URL + 'license': 'LGPL-3', + 'depends': [ + 'project', + 'hr_timesheet', + 'base', + ], + 'data': [ + 'security/security.xml', + 'security/ir.model.access.csv', + 'wizards/project_user_assign_wizard.xml', + 'view/teams.xml', + 'view/task_stages.xml', + 'view/project.xml', + 'view/project_task.xml', + ], + 'assets': { + }, + 'installable': True, + 'application': False, + 'auto_install': False, + 'post_init_hook': 'post_init_hook', +} diff --git a/addons_extensions/project_task_timesheet_extended/data/ir_sequence_data.xml b/addons_extensions/project_task_timesheet_extended/data/ir_sequence_data.xml new file mode 100644 index 000000000..75cafa13d --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/data/ir_sequence_data.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/addons_extensions/project_task_timesheet_extended/hooks.py b/addons_extensions/project_task_timesheet_extended/hooks.py new file mode 100644 index 000000000..6ee15a629 --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/hooks.py @@ -0,0 +1,142 @@ +# hooks.py +from odoo import api, SUPERUSER_ID + +def post_init_hook(env): + # Create shared project sequence if it doesn't exist + project_sequence = env['ir.sequence'].sudo().search([('code', '=', 'project.project.sequence')], limit=1) + if not project_sequence: + project_sequence = env['ir.sequence'].sudo().create({ + 'name': "Project Sequence", + 'implementation': 'no_gap', + 'padding': 3, + 'use_date_range': False, + 'prefix': 'PROJ-', + 'code': 'project.project.sequence', + }) + + public_rule = env.ref('project.project_public_members_rule', raise_if_not_found=False) + if public_rule: + public_rule.write({ + 'perm_read': True, + 'perm_write': False, + 'perm_create': False, + 'perm_unlink': False, + }) + private_task_rule = env.ref('project.ir_rule_private_task', raise_if_not_found=False) + if private_task_rule: + private_task_rule.write({ + 'domain_force': """[ + '&', + ('project_id', '!=', False), + ('project_id.privacy_visibility', '!=', 'followers'), + '|', + '&', + ('is_generic', '=', True), + '|', '|', + ('project_id', '!=', False), + ('parent_id', '!=', False), + ('user_ids', 'in', user.id), + '&', + ('is_generic', '=', False), + ('user_ids', 'in', user.id), + ] + """ + }) + + task_visibility_rule = env.ref('project.task_visibility_rule', raise_if_not_found=False) + if task_visibility_rule: + task_visibility_rule.write({ + 'domain_force' : """ + [ + '|', + '&', + ('project_id', '!=', False), + '|', '|', + '&', + ('project_id.privacy_visibility', '!=', 'followers'), + '|', + ('is_generic', '=', True), + ('user_ids', 'in', user.id), + '&', + ('project_id.message_partner_ids', 'in', [user.partner_id.id]), + ('is_generic', '=', True), + '&', + ('user_ids', 'in', user.id), + ('project_id.message_partner_ids', 'in', [user.partner_id.id]), + '&', + ('project_id', '=', False), + '|', + ('message_partner_ids', 'in', [user.partner_id.id]), + ('user_ids', 'in', user.id), + ] + + """ + }) + + task_visibility_rule_project_user = env.ref('project.task_visibility_rule_project_user', raise_if_not_found=False) + if task_visibility_rule_project_user: + task_visibility_rule_project_user.write({ + 'domain_force': """ + [ + '|', + '&', + ('project_id', '!=', False), + '|', '|', + '&', + ('project_id.privacy_visibility', '!=', 'followers'), + '|', + ('is_generic', '=', True), + ('user_ids', 'in', user.id), + '&', + ('project_id.message_partner_ids', 'in', [user.partner_id.id]), + ('is_generic', '=', True), + '&', + ('user_ids', 'in', user.id), + ('project_id.message_partner_ids', 'in', [user.partner_id.id]), + '&', + ('project_id', '=', False), + '|', + ('message_partner_ids', 'in', [user.partner_id.id]), + ('user_ids', 'in', user.id), + ] + + """ + }) + + # Get all projects without sequence_name, sorted by creation date + projects = env['project.project'].search([('sequence_name', '=', False)], order='create_date asc') + + # Assign sequence numbers to projects + for project in projects: + project.sequence_name = project_sequence.next_by_id() + + # Process all projects to ensure they have task sequences + all_projects = env['project.project'].search([]) + for project in all_projects: + if not project.task_sequence_id: + task_sequence = env['ir.sequence'].sudo().create({ + 'name': f"Task Sequence for {project.sequence_name}", + 'implementation': 'no_gap', + 'padding': 3, + 'use_date_range': False, + 'prefix': f"{project.sequence_name}/TASK-", + }) + project.task_sequence_id = task_sequence + + # Get all tasks without sequence_name, grouped by project + tasks = env['project.task'].search([('sequence_name', '=', False)], order='create_date asc') + + # Group tasks by project + project_tasks = {} + for task in tasks: + if task.project_id: + if task.project_id.id not in project_tasks: + project_tasks[task.project_id.id] = [] + project_tasks[task.project_id.id].append(task) + + # Assign sequence numbers to tasks + for project_id, task_list in project_tasks.items(): + project = env['project.project'].browse(project_id) + if project.task_sequence_id: + for task in task_list: + task.sequence_name = project.task_sequence_id.next_by_id() \ No newline at end of file diff --git a/addons_extensions/project_task_timesheet_extended/models/__init__.py b/addons_extensions/project_task_timesheet_extended/models/__init__.py new file mode 100644 index 000000000..000e815e7 --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/models/__init__.py @@ -0,0 +1,4 @@ +from . import teams +from . import task_stages +from . import project +from . import project_task \ No newline at end of file diff --git a/addons_extensions/project_task_timesheet_extended/models/project.py b/addons_extensions/project_task_timesheet_extended/models/project.py new file mode 100644 index 000000000..cb0244d2b --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/models/project.py @@ -0,0 +1,61 @@ +from odoo import api, fields, models, _ + + +class ProjectProject(models.Model): + _inherit = 'project.project' + + sequence_name = fields.Char("Project Number", copy=False, readonly=True) + task_sequence_id = fields.Many2one( + 'ir.sequence', + string="Task Sequence", + readonly=True, + copy=False, + help="Sequence for tasks of this project" + ) + + @api.model + def _get_shared_project_sequence(self): + """Get or create a shared sequence for all projects""" + sequence = self.env['ir.sequence'].sudo().search([('code', '=', 'project.project.sequence')], limit=1) + if not sequence: + sequence = self.env['ir.sequence'].sudo().create({ + 'name': _("Project Sequence"), + 'implementation': 'no_gap', + 'padding': 3, + 'use_date_range': False, + 'prefix': 'PROJ-', + 'code': 'project.project.sequence', + }) + return sequence + + @api.model_create_multi + def create(self, vals_list): + projects = super().create(vals_list) + sequence = self._get_shared_project_sequence() + for project in projects: + if not project.sequence_name: + project.sequence_name = sequence.next_by_id() + return projects + + project_lead = fields.Many2one("res.users", string="Project Lead") + members_ids = fields.Many2many('res.users', 'project_user_rel', 'project_id', + 'user_id', 'Project Members', help="""Project's + members are users who can have an access to + the tasks related to this project.""" + ) + user_id = fields.Many2one('res.users', string='Project Manager', default=lambda self: self.env.user, tracking=True, + domain=lambda self: [('groups_id', 'in', [self.env.ref('project.group_project_manager').id,self.env.ref('project_task_timesheet_extended.group_project_supervisor').id]),('share','=',False)],) + + + def add_users(self): + return { + 'type': 'ir.actions.act_window', + 'name': 'Add Users', + 'res_model': 'project.user.assign.wizard', + 'view_mode': 'form', + 'view_id': self.env.ref('project_task_timesheet_extended.project_user_assignment_form_view').id, + 'target': 'new', + 'context': {'default_members_ids':[(6, 0, self.members_ids.ids)], + }, + } + diff --git a/addons_extensions/project_task_timesheet_extended/models/project_task.py b/addons_extensions/project_task_timesheet_extended/models/project_task.py new file mode 100644 index 000000000..4af1a847b --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/models/project_task.py @@ -0,0 +1,48 @@ +from odoo import api, fields, models, _ + +class projectTask(models.Model): + _inherit = 'project.task' + _rec_name = 'name' + + sequence_name = fields.Char("Sequence", copy=False) + is_generic = fields.Boolean(string='Generic',default=True, help='All the followers would be able to see this task if the generic is set to true else only the assigned users would have the access to it') + assigned_team = fields.Many2one("internal.teams") + + @api.onchange("assigned_team") + def onchange_assigned_team(self): + for rec in self: + if rec.assigned_team: + user_ids = rec.assigned_team.members_ids.ids + if rec.assigned_team.team_lead: + user_ids.append(rec.assigned_team.team_lead.id) + rec.user_ids = [(6, 0, user_ids)] + else: + rec.user_ids = [(5, 0, 0)] + + @api.model_create_multi + def create(self, vals_list): + tasks = super().create(vals_list) + # Group tasks by project to avoid creating multiple sequences for the same project + project_dict = {} + for task in tasks: + if task.project_id: + if task.project_id.id not in project_dict: + project_dict[task.project_id.id] = task.project_id + + # Create task sequences for projects that don't have one + for project in project_dict.values(): + if not project.task_sequence_id: + task_sequence = self.env['ir.sequence'].sudo().create({ + 'name': _("Task Sequence for %s") % project.sequence_name, + 'implementation': 'no_gap', + 'padding': 3, + 'use_date_range': False, + 'prefix': f"{project.sequence_name}/TASK-", + }) + project.task_sequence_id = task_sequence + + # Assign sequence numbers to tasks + for task in tasks: + if task.project_id and task.project_id.task_sequence_id: + task.sequence_name = task.project_id.task_sequence_id.next_by_id() + return tasks diff --git a/addons_extensions/project_task_timesheet_extended/models/task_stages.py b/addons_extensions/project_task_timesheet_extended/models/task_stages.py new file mode 100644 index 000000000..c8e629713 --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/models/task_stages.py @@ -0,0 +1,7 @@ +from odoo import api, fields, models, _ + +class TaskStages(models.Model): + _inherit = 'project.task.type' + + team_id = fields.Many2one('internal.teams','Assigned to') + approval_by = fields.Selection([('assigned_team_lead','Assigned Team Lead'),('project_manager','Project Manager'),('project_lead','Project Lead')]) \ No newline at end of file diff --git a/addons_extensions/project_task_timesheet_extended/models/teams.py b/addons_extensions/project_task_timesheet_extended/models/teams.py new file mode 100644 index 000000000..8c7facac6 --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/models/teams.py @@ -0,0 +1,19 @@ +from odoo import api, fields, models + +class InternalTeams(models.Model): + _name = "internal.teams" + + team_name = fields.Text("Team Name", required=True) + team_lead = fields.Many2one("res.users", string="Team Lead") + members_ids = fields.Many2many('res.users', 'internal_team_user_rel', 'team_id', + 'user_id', 'Team Members', help="""Team Members are the users who are working in this particular team.""" + ) + active = fields.Boolean(default=True, help="Set active to false to hide the Teams without removing it.") + + + + def _compute_display_name(self): + """ Custom display_name in case a registration is nott linked to an attendee + """ + for registration in self: + registration.display_name = registration.team_name \ No newline at end of file diff --git a/addons_extensions/project_task_timesheet_extended/security/ir.model.access.csv b/addons_extensions/project_task_timesheet_extended/security/ir.model.access.csv new file mode 100644 index 000000000..7a3a73591 --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/security/ir.model.access.csv @@ -0,0 +1,12 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +internal_teams_admin,internal.teams.admin,model_internal_teams,project.group_project_manager,1,1,1,1 +internal_teams_manager,internal.teams.manager,model_internal_teams,project.group_project_user,1,1,1,0 +internal_teams_user,internal.teams.user,model_internal_teams,base.group_user,1,0,0,0 + +project_user_assign_wizard_manager,project.user.assign.wizard,model_project_user_assign_wizard,project.group_project_manager,1,1,1,1 + + +access_project_project_supervisor,project.project,project.model_project_project,project_task_timesheet_extended.group_project_supervisor,1,1,1,0 +access_project_project_stage_supervisor,project.project_stage.supervisor,project.model_project_project_stage,project_task_timesheet_extended.group_project_supervisor,1,1,1,0 +access_project_task_type_supervisor,project.task.type supervisor,project.model_project_task_type,project_task_timesheet_extended.group_project_supervisor,1,1,1,1 +access_project_tags_supervisor,project.project_tags_supervisor,project.model_project_tags,project_task_timesheet_extended.group_project_supervisor,1,1,1,1 diff --git a/addons_extensions/project_task_timesheet_extended/security/security.xml b/addons_extensions/project_task_timesheet_extended/security/security.xml new file mode 100644 index 000000000..ec6ac9ab6 --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/security/security.xml @@ -0,0 +1,70 @@ + + + + Manager + + + + + + + + + + + Manager: Own Projects + + + [('user_id', '=', user.id)] + + + + + + + + Project/Task: project supervisor: see all tasks linked to his assigned project or its own tasks + + [ + ('project_id.user_id','=',user.id), + '|', ('project_id', '!=', False), + ('user_ids', 'in', user.id), + ] + + + + + Project/Task: project users: don't see non generic tasks + + [ + '&', '&', + ('project_id', '!=', False), + ('is_generic', '=', False), + ('user_ids', 'not in', user.id), + ] + + + + + + + Project/Task: project lead: see all tasks + + [ + '&', '&', '&', + ('project_id', '!=', False), + ('project_id.project_lead', '=', user.id), + '|', ('is_generic', '=', True), ('is_generic', '=', False), + '|', ('user_ids', 'in', user.id), ('user_ids', 'not in', user.id) + ] + + + + + + + + + + + \ No newline at end of file diff --git a/addons_extensions/project_task_timesheet_extended/view/project.xml b/addons_extensions/project_task_timesheet_extended/view/project.xml new file mode 100644 index 000000000..9ff099542 --- /dev/null +++ b/addons_extensions/project_task_timesheet_extended/view/project.xml @@ -0,0 +1,39 @@ + + + + project.project.inherit.form.view + project.project + + + + +

+ +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/third_party_addons/dodger_blue/static/src/xml/sidebar_menu_icon_templates.xml b/third_party_addons/dodger_blue/static/src/xml/sidebar_menu_icon_templates.xml index 98b42246c..031e0a796 100644 --- a/third_party_addons/dodger_blue/static/src/xml/sidebar_menu_icon_templates.xml +++ b/third_party_addons/dodger_blue/static/src/xml/sidebar_menu_icon_templates.xml @@ -3,64 +3,64 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -125,9 +125,23 @@ - + + + diff --git a/third_party_addons/dodger_blue/views/ir_menu.xml b/third_party_addons/dodger_blue/views/ir_menu.xml index 13497c9a6..e9377a906 100644 --- a/third_party_addons/dodger_blue/views/ir_menu.xml +++ b/third_party_addons/dodger_blue/views/ir_menu.xml @@ -7,8 +7,10 @@ ir.ui.menu - - + + + +