from odoo import api, fields, models, _ from odoo.exceptions import UserError, ValidationError 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" ) discuss_channel_id = fields.Many2one( 'discuss.channel', string="Channel", domain="[('parent_channel_id', '=', default_projects_channel_id)]", help="Select a channel for project communications. Channels must be sub-channels of the main Projects Channel." ) default_projects_channel_id = fields.Many2one( 'discuss.channel', default=lambda self: self._get_default_projects_channel(), string="Default Projects Channel" ) @api.model def _get_default_projects_channel(self): """Get or create the default Projects Channel""" channel = self.env['discuss.channel'].search([ ('name', '=', 'Projects Channel'), ('channel_type', '=', 'channel') ], limit=1) if not channel: channel = self.env['discuss.channel'].create({ 'name': 'Projects Channel', 'description': 'Main channel for all project communications', 'channel_type': 'channel', }) return channel def action_create_project_channel(self): """Create a new channel for this project under the Projects Channel""" self.ensure_one() if self.discuss_channel_id: raise UserError(_("This project already has a channel assigned.")) # Create new channel channel_vals = { 'name': self.name, 'description': _("Communication channel for project %s") % self.name, 'channel_type': 'channel', 'parent_channel_id': self.default_projects_channel_id.id, } new_channel = self.env['discuss.channel'].create(channel_vals) self.discuss_channel_id = new_channel.id # Add project members to the channel self._add_project_members_to_channel() return { 'type': 'ir.actions.act_window', 'res_model': 'discuss.channel', 'res_id': new_channel.id, 'view_mode': 'form', 'target': 'current', 'context': {'create': False} } def _add_project_members_to_channel(self): """Add all project members as followers of the channel""" if not self.discuss_channel_id: return # Get all users related to this project members_to_add = self.env['res.users'] # Add project members if self.members_ids: members_to_add |= self.members_ids # Add project manager if self.user_id: members_to_add |= self.user_id # Add project lead if exists if hasattr(self, 'project_lead') and self.project_lead: members_to_add |= self.project_lead # Add members to channel for member in members_to_add: self.discuss_channel_id.add_members(member.partner_id.ids) def write(self, vals): """Override write to update channel members when project members change""" result = super().write(vals) # If members changed, update channel members if any(field in vals for field in ['members_ids', 'user_id', 'project_lead']): for project in self: if project.discuss_channel_id: project._add_project_members_to_channel() return result @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() if project.discuss_channel_id: project._add_project_members_to_channel() return projects def _default_type_ids(self): default_stage_ids = [ self.env.ref('project_task_timesheet_extended.task_type_backlog').id, self.env.ref('project_task_timesheet_extended.task_type_development').id, self.env.ref('project_task_timesheet_extended.task_type_code_review_and_merging').id, self.env.ref('project_task_timesheet_extended.task_type_testing').id, self.env.ref('project_task_timesheet_extended.task_type_deployment').id, self.env.ref('project_task_timesheet_extended.task_type_completed').id, ] # self.env.ref('project_task_timesheet_extended.task_type_cancelled').id, # self.env.ref('project_task_timesheet_extended.task_type_hold').id, return self.env['project.task.type'].browse(default_stage_ids) 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)],) type_ids = fields.Many2many(default=lambda self: self._default_type_ids()) estimated_hours = fields.Float(string="Estimated Hours") task_estimated_hours = fields.Float(string="Task Estimated Hours", compute="_compute_task_estimated_hours", store=True) actual_hours = fields.Float(string="Actual Hours", compute="_compute_actual_hours", store=True) @api.depends('task_ids.estimated_hours') def _compute_task_estimated_hours(self): for project in self: project.task_estimated_hours = sum(project.task_ids.mapped('estimated_hours')) @api.depends('task_ids.timesheet_ids.unit_amount') def _compute_actual_hours(self): for project in self: project.actual_hours = sum(project.task_ids.timesheet_ids.mapped('unit_amount')) 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)], }, }