@@ -402,7 +404,7 @@
Project Portfolios
project.portfolio
- list,kanban,form
+ kanban,list,form
Create your first portfolio
@@ -422,17 +424,30 @@
+
+
+
+ action="action_project_portfolio"
+ active="0"/>
diff --git a/addons_extensions/project_task_timesheet_extended/view/project_task_view.xml b/addons_extensions/project_task_timesheet_extended/view/project_task_view.xml
index 79cbe8b14..73e24caa8 100644
--- a/addons_extensions/project_task_timesheet_extended/view/project_task_view.xml
+++ b/addons_extensions/project_task_timesheet_extended/view/project_task_view.xml
@@ -116,8 +116,12 @@
create_date desc
+
+
+
+
diff --git a/addons_extensions/project_task_timesheet_extended/view/task_stages.xml b/addons_extensions/project_task_timesheet_extended/view/task_stages.xml
index 22420c122..7cd169569 100644
--- a/addons_extensions/project_task_timesheet_extended/view/task_stages.xml
+++ b/addons_extensions/project_task_timesheet_extended/view/task_stages.xml
@@ -1,16 +1,32 @@
-
-
- project.task.type.list.inherit
- project.task.type
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+ project.task.type.form.inherit.project.workflow
+ project.task.type
+
+
+
+
+
+
+
+
+
+
+
+
+
+ project.task.type.list.inherit
+ project.task.type
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons_extensions/project_task_timesheet_extended/wizards/project_stage_update_wizard.py b/addons_extensions/project_task_timesheet_extended/wizards/project_stage_update_wizard.py
index 28f571175..fce9a9925 100644
--- a/addons_extensions/project_task_timesheet_extended/wizards/project_stage_update_wizard.py
+++ b/addons_extensions/project_task_timesheet_extended/wizards/project_stage_update_wizard.py
@@ -1,96 +1,61 @@
-from odoo import models, fields, api
-
-class ProjectStageUpdateWizard(models.TransientModel):
- _name = 'project.stage.update.wizard'
- _description = 'Project Stage Update Wizard'
-
- project_id = fields.Many2one('project.project', string='Project', required=True)
- stage_id = fields.Many2one('project.task.type', string='Current Stage', required=True)
-
- name = fields.Char(string='Stage Name', related='stage_id.name', readonly=False)
- team_id = fields.Many2one('internal.teams', string='Assigned to', readonly=False)
- approval_by = fields.Selection([
- ('assigned_team_lead', 'Assigned Team Lead'),
- ('project_manager', 'Project Manager'),
- ('project_lead', 'Project Lead / Manager')
- ], readonly=False)
- fold = fields.Boolean(string='Folded in Kanban', readonly=False)
- involved_user_ids = fields.Many2many('res.users')
- related_user_ids = fields.Many2many(related="team_id.all_members_ids")
-
-
-
- @api.onchange('team_id')
- def onchange_team_id(self):
- for rec in self:
- if rec.team_id and rec.team_id.all_members_ids:
- if rec.team_id != rec.stage_id.team_id:
- rec.involved_user_ids = [(6,0,rec.team_id.all_members_ids.ids)]
- else:
- rec.involved_user_ids = [(5, 0)]
-
- def action_save_changes(self):
- """Create/update the stage and sync tasks and project links."""
- self.ensure_one()
- project = self.project_id
- old_stage = self.stage_id
-
- # Check if stage with same properties exists
- stages = self.env['project.task.type'].search([
- ('name', '=', self.name),
- ('team_id', '=', self.team_id.id),
- ('approval_by', '=', self.approval_by),
- ('fold', '=', self.fold),
- ('involved_user_ids','=',self.involved_user_ids.ids)
- ], limit=1)
-
- existing_stage = stages.filtered(
- lambda s: set(s.involved_user_ids.ids) == set(self.involved_user_ids.ids)
- )[:1]
-
- if existing_stage:
- existing_stage.sudo().write({
- 'project_ids': [(4, self.project_id.id)]
- })
- new_stage = existing_stage
-
- else:
- # Instead of copy(), create a clean new record without '(copy)'
- new_stage = self.env['project.task.type'].create({
- 'name': self.name,
- 'team_id': self.team_id.id,
- 'approval_by': self.approval_by ,
- 'fold': self.fold,
- 'sequence': old_stage.sequence, # optional: keep same order
- 'involved_user_ids': [(6,0,self.involved_user_ids.ids)],
- 'project_ids':[(6,0,self.project_id.ids)]
- })
-
- # If new_stage is different from old_stage → update references
- if new_stage.id != old_stage.id:
- # Update project type_ids
- type_ids = project.type_ids.ids
- if old_stage.id in type_ids:
- old_stage.sudo().write({
- 'project_ids':[(3,self.project_id.id)]
- })
- type_ids.remove(old_stage.id)
- if new_stage.id not in type_ids:
- type_ids.append(new_stage.id)
- project.type_ids = [(6, 0, type_ids)]
-
- # Update all tasks under this project with old stage
- tasks = self.env['project.task'].search([
- ('project_id', '=', project.id),
- ('stage_id', '=', old_stage.id)
- ])
- tasks.write({'stage_id': new_stage.id})
-
- # If the old stage is no longer used in any project or task → remove it
- remaining_projects = self.env['project.project'].search_count([('type_ids', 'in', old_stage.id)])
- remaining_tasks = self.env['project.task'].search_count([('stage_id', '=', old_stage.id)])
- if remaining_projects == 0 and remaining_tasks == 0:
- old_stage.unlink()
-
- return {'type': 'ir.actions.act_window_close'}
+from odoo import models, fields, api
+
+class ProjectStageUpdateWizard(models.TransientModel):
+ _name = 'project.stage.update.wizard'
+ _description = 'Project Stage Update Wizard'
+
+ project_id = fields.Many2one('project.project', string='Project', required=True)
+ stage_id = fields.Many2one('project.task.type', string='Current Stage', required=True)
+
+ name = fields.Char(string='Stage Name')
+ team_id = fields.Many2one('internal.teams', string='Assigned Team', readonly=False)
+ approval_by = fields.Selection([
+ ('assigned_team_lead', 'Assigned Team Lead'),
+ ('project_manager', 'Project Manager'),
+ ('project_lead', 'Project Lead / Manager')
+ ], string='Approval Owner', readonly=False)
+ fold = fields.Boolean(string='Folded in Kanban', readonly=False)
+ involved_user_ids = fields.Many2many('res.users', string='Related Users')
+ related_user_ids = fields.Many2many(related="team_id.all_members_ids")
+
+ @api.model
+ def default_get(self, fields_list):
+ values = super().default_get(fields_list)
+ stage = self.env['project.task.type'].browse(values.get('stage_id')).exists()
+ if stage:
+ values.setdefault('name', stage.name)
+ values.setdefault('team_id', stage.team_id.id)
+ values.setdefault('approval_by', stage.approval_by)
+ values.setdefault('fold', stage.fold)
+ values.setdefault('involved_user_ids', [(6, 0, stage.involved_user_ids.ids)])
+ return values
+
+ @api.onchange('team_id')
+ def onchange_team_id(self):
+ for rec in self:
+ if rec.team_id and rec.team_id.all_members_ids:
+ rec.involved_user_ids = [(6,0,rec.team_id.all_members_ids.ids)]
+ else:
+ rec.involved_user_ids = [(5, 0)]
+
+ def action_save_changes(self):
+ """Update the project-owned stage configuration safely."""
+ self.ensure_one()
+ project = self.project_id
+ project._ensure_project_owned_task_stages()
+ stage = project.type_ids.filtered(lambda record: record.id == self.stage_id.id)[:1]
+ if not stage:
+ stage = project._clone_task_stage_for_project(self.stage_id)
+ project.type_ids = [(4, stage.id)]
+
+ stage.write({
+ 'name': self.name,
+ 'team_id': self.team_id.id,
+ 'approval_by': self.approval_by,
+ 'fold': self.fold,
+ 'involved_user_ids': [(6, 0, self.involved_user_ids.ids)],
+ 'is_workflow_template': False,
+ })
+
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/addons_extensions/project_task_timesheet_extended/wizards/project_stage_update_wizard.xml b/addons_extensions/project_task_timesheet_extended/wizards/project_stage_update_wizard.xml
index 74d1620ba..766b75988 100644
--- a/addons_extensions/project_task_timesheet_extended/wizards/project_stage_update_wizard.xml
+++ b/addons_extensions/project_task_timesheet_extended/wizards/project_stage_update_wizard.xml
@@ -5,16 +5,16 @@
project.stage.update.wizard