project updates and changes
This commit is contained in:
parent
1b21175e75
commit
ce93d9601c
|
|
@ -109,42 +109,48 @@
|
|||
</record>
|
||||
</data>
|
||||
<data noupdate="1">
|
||||
<record id="task_type_backlog" model="project.task.type">
|
||||
<field name="sequence">100</field>
|
||||
<field name="name">Backlog</field>
|
||||
<field name="fold" eval="False"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
</record>
|
||||
<record id="task_type_development" model="project.task.type">
|
||||
<field name="sequence">101</field>
|
||||
<field name="name">Development</field>
|
||||
<field name="fold" eval="False"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
</record>
|
||||
<record id="task_type_code_review_and_merging" model="project.task.type">
|
||||
<field name="sequence">102</field>
|
||||
<field name="name">Code Review & Git Merging</field>
|
||||
<field name="fold" eval="False"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
</record>
|
||||
<record id="task_type_testing" model="project.task.type">
|
||||
<field name="sequence">103</field>
|
||||
<field name="name">Testing</field>
|
||||
<field name="fold" eval="False"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
</record>
|
||||
<record id="task_type_deployment" model="project.task.type">
|
||||
<field name="sequence">104</field>
|
||||
<field name="name">Deployment</field>
|
||||
<field name="fold" eval="False"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
</record>
|
||||
<record id="task_type_completed" model="project.task.type">
|
||||
<field name="sequence">105</field>
|
||||
<field name="name">Completed</field>
|
||||
<field name="fold" eval="True"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
</record>
|
||||
<record id="task_type_backlog" model="project.task.type">
|
||||
<field name="sequence">100</field>
|
||||
<field name="name">Backlog</field>
|
||||
<field name="fold" eval="False"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="is_workflow_template" eval="True"/>
|
||||
</record>
|
||||
<record id="task_type_development" model="project.task.type">
|
||||
<field name="sequence">101</field>
|
||||
<field name="name">Development</field>
|
||||
<field name="fold" eval="False"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="is_workflow_template" eval="True"/>
|
||||
</record>
|
||||
<record id="task_type_code_review_and_merging" model="project.task.type">
|
||||
<field name="sequence">102</field>
|
||||
<field name="name">Code Review & Git Merging</field>
|
||||
<field name="fold" eval="False"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="is_workflow_template" eval="True"/>
|
||||
</record>
|
||||
<record id="task_type_testing" model="project.task.type">
|
||||
<field name="sequence">103</field>
|
||||
<field name="name">Testing</field>
|
||||
<field name="fold" eval="False"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="is_workflow_template" eval="True"/>
|
||||
</record>
|
||||
<record id="task_type_deployment" model="project.task.type">
|
||||
<field name="sequence">104</field>
|
||||
<field name="name">Deployment</field>
|
||||
<field name="fold" eval="False"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="is_workflow_template" eval="True"/>
|
||||
</record>
|
||||
<record id="task_type_completed" model="project.task.type">
|
||||
<field name="sequence">105</field>
|
||||
<field name="name">Completed</field>
|
||||
<field name="fold" eval="True"/>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="is_workflow_template" eval="True"/>
|
||||
</record>
|
||||
<!-- <record id="task_type_cancelled" model="project.task.type">-->
|
||||
<!-- <field name="sequence">106</field>-->
|
||||
<!-- <field name="name">Cancelled</field>-->
|
||||
|
|
@ -257,4 +263,4 @@
|
|||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -162,9 +162,12 @@ def post_init_hook(env):
|
|||
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()
|
||||
# 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()
|
||||
|
||||
# Normalize task stages so each project owns its workflow configuration.
|
||||
env['project.project'].search([])._ensure_project_owned_task_stages()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from odoo import api, fields, models, _
|
||||
from odoo import Command, api, fields, models, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from markupsafe import Markup
|
||||
from datetime import datetime, timedelta
|
||||
|
|
@ -899,9 +899,137 @@ class ProjectProject(models.Model):
|
|||
for member in members_to_add:
|
||||
self.discuss_channel_id.add_members(member.partner_id.ids)
|
||||
|
||||
def _get_default_task_stage_templates(self):
|
||||
template_xmlids = [
|
||||
'project_task_timesheet_extended.task_type_backlog',
|
||||
'project_task_timesheet_extended.task_type_development',
|
||||
'project_task_timesheet_extended.task_type_code_review_and_merging',
|
||||
'project_task_timesheet_extended.task_type_testing',
|
||||
'project_task_timesheet_extended.task_type_deployment',
|
||||
'project_task_timesheet_extended.task_type_completed',
|
||||
]
|
||||
stages = self.env['project.task.type']
|
||||
for xmlid in template_xmlids:
|
||||
stage = self.env.ref(xmlid, raise_if_not_found=False)
|
||||
if stage:
|
||||
stages |= stage
|
||||
return stages.sorted('sequence')
|
||||
|
||||
def _clone_task_stage_for_project(self, stage):
|
||||
self.ensure_one()
|
||||
new_stage = self.env['project.task.type'].create(stage._prepare_project_owned_stage_vals(self))
|
||||
project_tasks = self.env['project.task'].search([
|
||||
('project_id', '=', self.id),
|
||||
('stage_id', '=', stage.id),
|
||||
])
|
||||
if project_tasks:
|
||||
project_tasks.write({'stage_id': new_stage.id})
|
||||
return new_stage
|
||||
|
||||
def _ensure_project_owned_task_stages(self):
|
||||
if len(self) > 1:
|
||||
for project_id in self.ids:
|
||||
self.browse(project_id)._ensure_project_owned_task_stages()
|
||||
return
|
||||
|
||||
self.ensure_one()
|
||||
if not self.type_ids:
|
||||
self.type_ids = [Command.set(self._get_default_task_stage_templates().ids)]
|
||||
|
||||
owned_stage_ids = []
|
||||
for stage in self.type_ids.sorted('sequence'):
|
||||
if stage._is_project_owned_stage(self):
|
||||
owned_stage_ids.append(stage.id)
|
||||
continue
|
||||
cloned_stage = self._clone_task_stage_for_project(stage)
|
||||
owned_stage_ids.append(cloned_stage.id)
|
||||
|
||||
if self.type_ids.ids != owned_stage_ids:
|
||||
super(ProjectProject, self).write({'type_ids': [Command.set(owned_stage_ids)]})
|
||||
|
||||
def _prepare_type_ids_commands(self, commands):
|
||||
self.ensure_one()
|
||||
if not commands:
|
||||
return commands
|
||||
|
||||
processed_commands = []
|
||||
stage_model = self.env['project.task.type']
|
||||
|
||||
for command in commands:
|
||||
if not isinstance(command, (list, tuple)) or not command:
|
||||
processed_commands.append(command)
|
||||
continue
|
||||
|
||||
operation = command[0]
|
||||
|
||||
if operation == Command.CREATE:
|
||||
values = dict(command[2] or {})
|
||||
values['project_ids'] = [Command.set(self.ids)]
|
||||
values.setdefault('is_workflow_template', False)
|
||||
processed_commands.append(Command.create(values))
|
||||
continue
|
||||
|
||||
if operation == Command.UPDATE:
|
||||
stage = stage_model.browse(command[1]).exists()
|
||||
if not stage:
|
||||
continue
|
||||
if not stage._is_project_owned_stage(self):
|
||||
original_stage = stage
|
||||
stage = self._clone_task_stage_for_project(stage)
|
||||
processed_commands.extend([
|
||||
Command.unlink(original_stage.id),
|
||||
Command.link(stage.id),
|
||||
])
|
||||
processed_commands.append(Command.update(stage.id, command[2] or {}))
|
||||
continue
|
||||
|
||||
if operation == Command.LINK:
|
||||
stage = stage_model.browse(command[1]).exists()
|
||||
if stage and not stage._is_project_owned_stage(self):
|
||||
stage = self._clone_task_stage_for_project(stage)
|
||||
if stage:
|
||||
processed_commands.append(Command.link(stage.id))
|
||||
continue
|
||||
|
||||
if operation == Command.SET:
|
||||
stage_ids = []
|
||||
for stage in stage_model.browse(command[2]).exists():
|
||||
if not stage._is_project_owned_stage(self):
|
||||
stage = self._clone_task_stage_for_project(stage)
|
||||
stage_ids.append(stage.id)
|
||||
processed_commands.append(Command.set(stage_ids))
|
||||
continue
|
||||
|
||||
if operation == Command.DELETE:
|
||||
stage = stage_model.browse(command[1]).exists()
|
||||
if stage and not stage._is_project_owned_stage(self):
|
||||
processed_commands.append(Command.unlink(stage.id))
|
||||
else:
|
||||
processed_commands.append(command)
|
||||
continue
|
||||
|
||||
processed_commands.append(command)
|
||||
|
||||
return processed_commands
|
||||
|
||||
def write(self, vals):
|
||||
"""Override write to update channel members when project members change"""
|
||||
result = super().write(vals)
|
||||
if 'type_ids' in vals:
|
||||
if len(self) > 1:
|
||||
results = []
|
||||
for project in self:
|
||||
project_vals = dict(vals)
|
||||
project_vals['type_ids'] = project._prepare_type_ids_commands(vals['type_ids'])
|
||||
results.append(super(ProjectProject, project).write(project_vals))
|
||||
project._ensure_project_owned_task_stages()
|
||||
result = all(results)
|
||||
else:
|
||||
vals = dict(vals)
|
||||
vals['type_ids'] = self._prepare_type_ids_commands(vals['type_ids'])
|
||||
result = super().write(vals)
|
||||
self._ensure_project_owned_task_stages()
|
||||
else:
|
||||
result = super().write(vals)
|
||||
|
||||
# If members changed, update channel members
|
||||
if any(field in vals for field in ['members_ids', 'user_id', 'project_lead']):
|
||||
|
|
@ -944,6 +1072,7 @@ class ProjectProject(models.Model):
|
|||
projects = super().create(vals_list)
|
||||
sequence = self._get_shared_project_sequence()
|
||||
for project in projects:
|
||||
project._ensure_project_owned_task_stages()
|
||||
if not project.sequence_name:
|
||||
project.sequence_name = sequence.next_by_id()
|
||||
if project.discuss_channel_id:
|
||||
|
|
@ -951,18 +1080,7 @@ class ProjectProject(models.Model):
|
|||
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)
|
||||
return self._get_default_task_stage_templates()
|
||||
|
||||
|
||||
project_lead = fields.Many2one("res.users", string="Project Lead",
|
||||
|
|
@ -975,10 +1093,10 @@ class ProjectProject(models.Model):
|
|||
user_id = fields.Many2one('res.users', string='Project Manager', default=False, tracking=True, #required = True,
|
||||
domain=lambda self: [('id','in',self.env.ref('project_task_timesheet_extended.role_project_manager').user_ids.ids),('groups_id', 'in', [self.env.ref('project.group_project_manager').id,self.env.ref('project_task_timesheet_extended.group_project_supervisor').id]),('share','=',False)],)
|
||||
|
||||
@api.constrains('user_id', 'selected_employee_id')
|
||||
@api.constrains('user_id')
|
||||
def _check_team_lead_before_members(self):
|
||||
for rec in self:
|
||||
if rec.selected_employee_id and not rec.user_id:
|
||||
if not rec.user_id:
|
||||
raise ValidationError("Assign Project Manager before adding members")
|
||||
|
||||
type_ids = fields.Many2many(default=lambda self: self._default_type_ids())
|
||||
|
|
@ -1081,4 +1199,4 @@ class ProjectTask(models.Model):
|
|||
def action_show_project_task_chatter(self):
|
||||
"""Toggle visibility of project chatter"""
|
||||
for project in self:
|
||||
project.show_task_chatter = not project.show_task_chatter
|
||||
project.show_task_chatter = not project.show_task_chatter
|
||||
|
|
|
|||
|
|
@ -8,8 +8,12 @@ import pytz
|
|||
class ProjectStages(models.Model):
|
||||
_inherit = "project.project.stage"
|
||||
|
||||
approval_by = fields.Selection([('project_manager', 'Project Manager'), ('project_sponsor', 'Project Sponsor')])
|
||||
|
||||
approval_by = fields.Selection([
|
||||
('project_manager', 'Project Manager'),
|
||||
('project_lead', 'Project Lead'),
|
||||
('project_sponsor', 'Project Sponsor'),
|
||||
('manager_lead_or_sponsor', 'Project Authorizers'),
|
||||
])
|
||||
|
||||
class projectStagesApprovalFlow(models.Model):
|
||||
_name = 'project.stages.approval.flow'
|
||||
|
|
@ -17,7 +21,7 @@ class projectStagesApprovalFlow(models.Model):
|
|||
stage_id = fields.Many2one('project.project.stage')
|
||||
stage_approval_by = fields.Selection(related='stage_id.approval_by')
|
||||
approval_by = fields.Many2one("res.users", domain="[('id','in',approval_by_users)]")
|
||||
assigned_to = fields.Many2one("res.users", domain="[('id','in',related_stage_users)]")
|
||||
assigned_to = fields.Many2one("res.users")
|
||||
related_stage_users = fields.Many2many("res.users",related="stage_id.user_ids")
|
||||
assigned_date = fields.Datetime()
|
||||
submission_date = fields.Datetime()
|
||||
|
|
@ -27,7 +31,7 @@ class projectStagesApprovalFlow(models.Model):
|
|||
manager_level_edit_access = fields.Boolean(compute="_compute_manager_level_edit_access")
|
||||
activate = fields.Boolean(default=True)
|
||||
involved_users = fields.Many2many('res.users', 'project_stage_approval_user_rel', 'project_stage_approval_id',
|
||||
'user_id',string="Related Users",domain="[('id','in',related_stage_users)]")
|
||||
'user_id',string="Related Users")
|
||||
|
||||
def _compute_manager_level_edit_access(self):
|
||||
for rec in self:
|
||||
|
|
@ -45,8 +49,14 @@ class projectStagesApprovalFlow(models.Model):
|
|||
|
||||
if rec.stage_approval_by == 'project_manager' and rec.project_id.user_id:
|
||||
pm_users = rec.project_id.user_id.ids
|
||||
|
||||
elif rec.stage_approval_by == 'project_lead' and rec.project_id.project_lead:
|
||||
pm_users = rec.project_id.project_lead.ids
|
||||
|
||||
elif rec.stage_approval_by == 'project_sponsor' and rec.project_id.project_sponsor:
|
||||
pm_users = rec.project_id.project_sponsor.ids
|
||||
elif rec.stage_approval_by == 'manager_lead_or_sponsor':
|
||||
pm_users = list(set(rec.project_id.user_id.ids + rec.project_id.project_lead.ids + rec.project_id.project_sponsor.ids))
|
||||
else:
|
||||
pm_users = self.env['res.users'].sudo().search([
|
||||
('groups_id', 'in', group_pm.id)
|
||||
|
|
|
|||
|
|
@ -62,9 +62,9 @@ class projectTask(models.Model):
|
|||
def write(self, vals):
|
||||
# Allow stage update for multiple records
|
||||
if 'stage_id' in vals:
|
||||
return super(ProjectTask, self).write(vals)
|
||||
return super(projectTask, self).write(vals)
|
||||
|
||||
return super(ProjectTask, self).write(vals)
|
||||
return super(projectTask, self).write(vals)
|
||||
|
||||
|
||||
@api.constrains('name')
|
||||
|
|
@ -110,7 +110,7 @@ class projectTask(models.Model):
|
|||
):
|
||||
raise UserError("Only Task Creator or Project Manager can edit Generic field.")
|
||||
|
||||
return super(ProjectTask, self).write(vals)
|
||||
return super(projectTask, self).write(vals)
|
||||
|
||||
@api.constrains('estimated_hours')
|
||||
def _check_estimated_hours(self):
|
||||
|
|
|
|||
|
|
@ -1,39 +1,73 @@
|
|||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
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 / Manager')])
|
||||
involved_user_ids = fields.Many2many('res.users')
|
||||
|
||||
@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)]
|
||||
|
||||
def create_or_update_data(self):
|
||||
"""Open wizard for updating this stage inside a project context."""
|
||||
self.ensure_one()
|
||||
project_id = self.env.context.get('project_id') or self.env.context.get('active_id')
|
||||
|
||||
if not project_id:
|
||||
raise UserError(_("No project found in context."))
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Edit Stage',
|
||||
'res_model': 'project.stage.update.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_project_id': project_id,
|
||||
'default_stage_id': self.id,
|
||||
'default_team_id': self.team_id.id if self.team_id else False,
|
||||
'default_approval_by': self.approval_by if self.approval_by else False,
|
||||
'default_fold': self.fold,
|
||||
'default_involved_user_ids': [(6,0,self.involved_user_ids.ids)]
|
||||
},
|
||||
}
|
||||
from odoo import Command, api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class TaskStages(models.Model):
|
||||
_inherit = 'project.task.type'
|
||||
|
||||
team_id = fields.Many2one('internal.teams', 'Assigned Team')
|
||||
approval_by = fields.Selection(
|
||||
[('assigned_team_lead', 'Assigned Team Lead'), ('project_manager', 'Project Manager'), ('project_lead', 'Project Lead / Manager')],
|
||||
string='Approval Owner',
|
||||
)
|
||||
involved_user_ids = fields.Many2many('res.users', string='Related Users')
|
||||
team_related_user_ids = fields.Many2many(related='team_id.all_members_ids', string='Team Users')
|
||||
is_workflow_template = fields.Boolean(
|
||||
string='Workflow Template Stage',
|
||||
default=False,
|
||||
copy=False,
|
||||
help='Technical flag used to keep template stages separate from project-owned stages.',
|
||||
)
|
||||
|
||||
@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, 0)]
|
||||
|
||||
def _is_project_owned_stage(self, project):
|
||||
self.ensure_one()
|
||||
return (
|
||||
not self.is_workflow_template
|
||||
and self.project_ids == project
|
||||
)
|
||||
|
||||
def _prepare_project_owned_stage_vals(self, project):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': self.name,
|
||||
'sequence': self.sequence,
|
||||
'project_ids': [Command.set(project.ids)],
|
||||
'mail_template_id': self.mail_template_id.id,
|
||||
'fold': self.fold,
|
||||
'rating_template_id': self.rating_template_id.id,
|
||||
'auto_validation_state': self.auto_validation_state,
|
||||
'active': self.active,
|
||||
'user_id': False,
|
||||
'team_id': self.team_id.id,
|
||||
'approval_by': self.approval_by,
|
||||
'involved_user_ids': [Command.set(self.involved_user_ids.ids)],
|
||||
'is_workflow_template': False,
|
||||
}
|
||||
|
||||
def create_or_update_data(self):
|
||||
"""Open the stage in form mode with the active project context."""
|
||||
self.ensure_one()
|
||||
project_id = self.env.context.get('project_id') or self.env.context.get('active_id')
|
||||
|
||||
if not project_id:
|
||||
raise UserError(_("No project found in context."))
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Edit Task Stage'),
|
||||
'res_model': 'project.task.type',
|
||||
'res_id': self.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_project_id': project_id,
|
||||
'project_stage_project_id': project_id,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,23 +113,21 @@
|
|||
<field name="activate"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page name="task_stages" string="Task Stages" invisible="not is_project_editor">
|
||||
<field name="type_ids" context="{'project_id': id}" options="{'no_open': True}">
|
||||
<list edit="0" no_open="True">
|
||||
<field name="sequence"/>
|
||||
<field name="name"/>
|
||||
<field name="team_id"/>
|
||||
<field name="approval_by"/>
|
||||
<field name="fold"/>
|
||||
<field name="involved_user_ids" widget="many2many_tags"/>
|
||||
<button name="create_or_update_data"
|
||||
type="object"
|
||||
string="Update"
|
||||
class="btn-primary"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</page>
|
||||
<page name="task_stages" string="Task Stages" invisible="not is_project_editor">
|
||||
<field name="type_ids" context="{'default_project_id': id, 'project_stage_project_id': id}">
|
||||
<list editable="bottom" open_form_view="True" delete="0">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="team_id" string="Assigned Team"/>
|
||||
<field name="approval_by" string="Approval Owner"/>
|
||||
<field name="fold"/>
|
||||
<field name="team_related_user_ids" invisible="1" column_invisible="1"/>
|
||||
<field name="involved_user_ids" widget="many2many_tags" domain="[('id', 'in', team_related_user_ids)]"/>
|
||||
<field name="is_workflow_template" invisible="1" column_invisible="1"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Team" name="team" invisible="not is_project_editor">
|
||||
<group>
|
||||
<button name="fetch_project_task_stage_users" type="object" string="Fetch Users from Related Stages" class="btn-primary"/>
|
||||
|
|
@ -886,15 +884,15 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- <record id="project_view_kanban_inherit" model="ir.ui.view">-->
|
||||
<!-- <field name="name">project.view.kanban.inherit</field>-->
|
||||
<!-- <field name="model">project.project</field>-->
|
||||
<!-- <field name="inherit_id" ref="project.view_project_kanban"/>-->
|
||||
<!-- <field name="arch" type="xml">-->
|
||||
<!-- <xpath expr="//kanban" position="attributes">-->
|
||||
<!-- <attribute name="context">{'view_type':'kanban'}</attribute>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- </field>-->
|
||||
<!-- </record>-->
|
||||
|
||||
</odoo>
|
||||
<!-- <record id="project_view_kanban_inherit" model="ir.ui.view">-->
|
||||
<!-- <field name="name">project.view.kanban.inherit</field>-->
|
||||
<!-- <field name="model">project.project</field>-->
|
||||
<!-- <field name="inherit_id" ref="project.view_project_kanban"/>-->
|
||||
<!-- <field name="arch" type="xml">-->
|
||||
<!-- <xpath expr="//kanban" position="attributes">-->
|
||||
<!-- <attribute name="context">{'view_type':'kanban'}</attribute>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- </field>-->
|
||||
<!-- </record>-->
|
||||
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -252,11 +252,13 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<span class="fa fa-folder-open me-1"/>
|
||||
<span>
|
||||
<t t-esc="record.project_ids.count"/>
|
||||
Projects
|
||||
</span>
|
||||
<a name="action_view_projects" type="object">
|
||||
<div>
|
||||
<span class="fa fa-folder-open me-1"/>
|
||||
<field name="project_count" class="o_value"/>
|
||||
<span class="o_stat_text"> Projects</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
|
|
@ -402,7 +404,7 @@
|
|||
<record id="action_project_portfolio" model="ir.actions.act_window">
|
||||
<field name="name">Project Portfolios</field>
|
||||
<field name="res_model">project.portfolio</field>
|
||||
<field name="view_mode">list,kanban,form</field>
|
||||
<field name="view_mode">kanban,list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create your first portfolio
|
||||
|
|
@ -422,17 +424,30 @@
|
|||
<menuitem id="menu_project_portfolio_root"
|
||||
name="Portfolios"
|
||||
parent="project.menu_main_pm"
|
||||
sequence="30"
|
||||
sequence="1"
|
||||
action="action_project_portfolio"
|
||||
groups="project.group_project_manager"/>
|
||||
|
||||
<menuitem
|
||||
name="Projects"
|
||||
id="project.menu_projects_group_stage"
|
||||
parent="project.menu_main_pm"
|
||||
action="project.open_view_project_all_group_stage"
|
||||
groups="project.group_project_stages"
|
||||
sequence="2"
|
||||
/>
|
||||
|
||||
|
||||
<menuitem id="menu_project_portfolio"
|
||||
name="Project Portfolios"
|
||||
parent="menu_project_portfolio_root"
|
||||
action="action_project_portfolio"/>
|
||||
action="action_project_portfolio"
|
||||
active="0"/>
|
||||
|
||||
<menuitem id="menu_project_portfolio_performance"
|
||||
name="Team Performance"
|
||||
parent="menu_project_portfolio_root"
|
||||
parent="project.menu_project_report"
|
||||
sequence="0"
|
||||
action="action_project_portfolio_employee_performance"/>
|
||||
|
||||
<!-- Inherit Project Form -->
|
||||
|
|
|
|||
|
|
@ -116,8 +116,12 @@
|
|||
|
||||
<xpath expr="//kanban" position="attributes">
|
||||
<attribute name="default_order">create_date desc</attribute>
|
||||
<attribute name="action"></attribute>
|
||||
<attribute name="on_create"></attribute>
|
||||
</xpath>
|
||||
|
||||
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="task_type_tree_inherit" model="ir.ui.view">
|
||||
<field name="name">project.task.type.list.inherit</field>
|
||||
<field name="model">project.task.type</field>
|
||||
<field name="inherit_id" ref="project.task_type_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='name']" position="after">
|
||||
<field name="team_id" optional="show"/>
|
||||
<field name="approval_by" optional="show"/>
|
||||
<field name="involved_user_ids" optional="show"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
<odoo>
|
||||
<record id="task_type_form_inherit_project_workflow" model="ir.ui.view">
|
||||
<field name="name">project.task.type.form.inherit.project.workflow</field>
|
||||
<field name="model">project.task.type</field>
|
||||
<field name="inherit_id" ref="project.task_type_edit"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='fold']" position="after">
|
||||
<field name="team_id"/>
|
||||
<field name="approval_by"/>
|
||||
<field name="team_related_user_ids" invisible="1"/>
|
||||
<field name="involved_user_ids" widget="many2many_tags" domain="[('id', 'in', team_related_user_ids)]"/>
|
||||
<field name="is_workflow_template" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="task_type_tree_inherit" model="ir.ui.view">
|
||||
<field name="name">project.task.type.list.inherit</field>
|
||||
<field name="model">project.task.type</field>
|
||||
<field name="inherit_id" ref="project.task_type_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='name']" position="after">
|
||||
<field name="team_id" optional="show"/>
|
||||
<field name="approval_by" optional="show"/>
|
||||
<field name="team_related_user_ids" invisible="1"/>
|
||||
<field name="involved_user_ids" optional="show" domain="[('id', 'in', team_related_user_ids)]"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,16 +5,16 @@
|
|||
<field name="model">project.stage.update.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Update Stage">
|
||||
<group>
|
||||
<field name="project_id" invisible="1"/>
|
||||
<field name="stage_id"/>
|
||||
<field name="name"/>
|
||||
<field name="team_id" required="approval_by == 'assigned_team_lead'"/>
|
||||
<field name="approval_by"/>
|
||||
<field name="involved_user_ids" widget="many2many_tags"/>
|
||||
<field name="related_user_ids" invisible="1"/>
|
||||
<field name="fold"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="project_id" invisible="1"/>
|
||||
<field name="stage_id"/>
|
||||
<field name="name"/>
|
||||
<field name="team_id" required="approval_by == 'assigned_team_lead'"/>
|
||||
<field name="approval_by"/>
|
||||
<field name="involved_user_ids" widget="many2many_tags" domain="[('id', 'in', related_user_ids)]"/>
|
||||
<field name="related_user_ids" invisible="1"/>
|
||||
<field name="fold"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Save Changes" name="action_save_changes" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" special="cancel" class="btn-secondary"/>
|
||||
|
|
@ -31,4 +31,4 @@
|
|||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
</odoo>
|
||||
|
|
|
|||
Loading…
Reference in New Issue