Merge pull request 'project internal users and roles changes' (#27) from feature/share_module into srivyn_test
Reviewed-on: https://gitea.ftprotech.in/administrator/odoo18/pulls/27
This commit is contained in:
commit
c047dcb8c3
|
|
@ -80,6 +80,21 @@ class ProjectProject(models.Model):
|
|||
project_stages = fields.One2many('project.stages.approval.flow', 'project_id')
|
||||
assign_approval_flow = fields.Boolean(default=False)
|
||||
project_sponsor = fields.Many2one('res.users')
|
||||
allowed_project_manager_user_ids = fields.Many2many(
|
||||
'res.users',
|
||||
compute='_compute_portfolio_allowed_users',
|
||||
string='Allowed Project Managers',
|
||||
)
|
||||
allowed_project_lead_user_ids = fields.Many2many(
|
||||
'res.users',
|
||||
compute='_compute_portfolio_allowed_users',
|
||||
string='Allowed Project Leads',
|
||||
)
|
||||
allowed_internal_team_ids = fields.Many2many(
|
||||
'internal.teams',
|
||||
compute='_compute_portfolio_allowed_teams',
|
||||
string='Allowed Internal Teams',
|
||||
)
|
||||
show_project_chatter = fields.Boolean(default=False)
|
||||
project_vision = fields.Text(
|
||||
string="Project Vision",
|
||||
|
|
@ -1173,6 +1188,117 @@ class ProjectProject(models.Model):
|
|||
def _default_type_ids(self):
|
||||
return self._get_default_task_stage_templates()
|
||||
|
||||
def _get_role_users_for_portfolio(self, role_xmlid):
|
||||
self.ensure_one()
|
||||
role = self.env.ref(role_xmlid, raise_if_not_found=False)
|
||||
if not role:
|
||||
return self.env['res.users']
|
||||
if self.portfolio_id:
|
||||
assignment = self.portfolio_id.role_assignment_ids.filtered(
|
||||
lambda line: line.role_id == role
|
||||
)[:1]
|
||||
if assignment:
|
||||
return assignment.user_ids
|
||||
return role.user_ids
|
||||
|
||||
def _get_portfolio_role_assignment(self, role_xmlid):
|
||||
self.ensure_one()
|
||||
role = self.env.ref(role_xmlid, raise_if_not_found=False)
|
||||
if not role or not self.portfolio_id:
|
||||
return self.env['project.portfolio.role.assignment']
|
||||
return self.portfolio_id.role_assignment_ids.filtered(
|
||||
lambda line: line.role_id == role
|
||||
)[:1]
|
||||
|
||||
@api.depends(
|
||||
'portfolio_id',
|
||||
'portfolio_id.role_assignment_ids.role_id',
|
||||
'portfolio_id.role_assignment_ids.user_ids',
|
||||
)
|
||||
def _compute_portfolio_allowed_users(self):
|
||||
manager_groups = (
|
||||
self.env.ref('project.group_project_manager')
|
||||
| self.env.ref('project_task_timesheet_extended.group_project_supervisor')
|
||||
)
|
||||
for project in self:
|
||||
manager_users = project._get_role_users_for_portfolio(
|
||||
'project_task_timesheet_extended.role_project_manager'
|
||||
)
|
||||
project.allowed_project_manager_user_ids = manager_users.filtered(
|
||||
lambda user: not user.share and bool(user.groups_id & manager_groups)
|
||||
)
|
||||
project.allowed_project_lead_user_ids = project._get_role_users_for_portfolio(
|
||||
'project_task_timesheet_extended.role_project_lead'
|
||||
)
|
||||
|
||||
@api.depends('portfolio_id', 'portfolio_id.internal_team_ids')
|
||||
def _compute_portfolio_allowed_teams(self):
|
||||
Team = self.env['internal.teams']
|
||||
fallback_teams = Team.search([('active', '=', True)])
|
||||
for project in self:
|
||||
project.allowed_internal_team_ids = (
|
||||
project.portfolio_id.internal_team_ids
|
||||
if project.portfolio_id and project.portfolio_id.internal_team_ids
|
||||
else fallback_teams
|
||||
)
|
||||
|
||||
@api.onchange('portfolio_id')
|
||||
def _onchange_portfolio_id_scope_assignments(self):
|
||||
for project in self:
|
||||
manager_assignment = project._get_portfolio_role_assignment(
|
||||
'project_task_timesheet_extended.role_project_manager'
|
||||
)
|
||||
lead_assignment = project._get_portfolio_role_assignment(
|
||||
'project_task_timesheet_extended.role_project_lead'
|
||||
)
|
||||
if (
|
||||
manager_assignment
|
||||
and project.user_id
|
||||
and project.user_id not in project.allowed_project_manager_user_ids
|
||||
):
|
||||
project.user_id = False
|
||||
if lead_assignment and project.project_lead:
|
||||
project.project_lead &= project.allowed_project_lead_user_ids
|
||||
if project.portfolio_id.internal_team_ids:
|
||||
for task_stage in project.type_ids:
|
||||
if task_stage.team_id and task_stage.team_id not in project.allowed_internal_team_ids:
|
||||
task_stage.team_id = False
|
||||
task_stage.involved_user_ids = [Command.clear()]
|
||||
|
||||
@api.constrains('portfolio_id', 'user_id', 'project_lead')
|
||||
def _check_portfolio_role_users(self):
|
||||
for project in self:
|
||||
manager_assignment = project._get_portfolio_role_assignment(
|
||||
'project_task_timesheet_extended.role_project_manager'
|
||||
)
|
||||
lead_assignment = project._get_portfolio_role_assignment(
|
||||
'project_task_timesheet_extended.role_project_lead'
|
||||
)
|
||||
if (
|
||||
manager_assignment
|
||||
and project.user_id
|
||||
and project.user_id not in project.allowed_project_manager_user_ids
|
||||
):
|
||||
raise ValidationError(_(
|
||||
'The project manager must be one of the users selected for the Project Manager role on this portfolio.'
|
||||
))
|
||||
if lead_assignment and project.project_lead - project.allowed_project_lead_user_ids:
|
||||
raise ValidationError(_(
|
||||
'Project leads must be selected from the Project Lead users configured on this portfolio.'
|
||||
))
|
||||
|
||||
@api.constrains('portfolio_id', 'type_ids', 'type_ids.team_id')
|
||||
def _check_portfolio_internal_teams(self):
|
||||
for project in self:
|
||||
if not project.portfolio_id.internal_team_ids:
|
||||
continue
|
||||
invalid_stages = project.type_ids.filtered(
|
||||
lambda stage: stage.team_id and stage.team_id not in project.allowed_internal_team_ids
|
||||
)
|
||||
if invalid_stages:
|
||||
raise ValidationError(_(
|
||||
'Task stage teams must be selected from the internal teams configured on this portfolio.'
|
||||
))
|
||||
|
||||
project_lead = fields.Many2many(
|
||||
"res.users",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from odoo import models, fields, api, _
|
||||
from datetime import date, timedelta
|
||||
import json
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from datetime import date, timedelta
|
||||
import json
|
||||
|
||||
|
||||
class ProjectPortfolio(models.Model):
|
||||
|
|
@ -23,11 +24,25 @@ class ProjectPortfolio(models.Model):
|
|||
|
||||
description = fields.Text(string='Description')
|
||||
|
||||
project_ids = fields.One2many(
|
||||
'project.project',
|
||||
'portfolio_id',
|
||||
string='Projects'
|
||||
)
|
||||
project_ids = fields.One2many(
|
||||
'project.project',
|
||||
'portfolio_id',
|
||||
string='Projects'
|
||||
)
|
||||
internal_team_ids = fields.Many2many(
|
||||
'internal.teams',
|
||||
'project_portfolio_internal_team_rel',
|
||||
'portfolio_id',
|
||||
'team_id',
|
||||
string='Internal Teams',
|
||||
help='Internal teams available for projects linked to this portfolio.'
|
||||
)
|
||||
role_assignment_ids = fields.One2many(
|
||||
'project.portfolio.role.assignment',
|
||||
'portfolio_id',
|
||||
string='Role Assignments',
|
||||
help='Portfolio-specific users available for each project role.'
|
||||
)
|
||||
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
|
|
@ -533,19 +548,73 @@ class ProjectPortfolio(models.Model):
|
|||
}
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
_inherit = 'project.project'
|
||||
|
||||
portfolio_id = fields.Many2one(
|
||||
'project.portfolio',
|
||||
string='Portfolio'
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ProjectPortfolioEmployeePerformance(models.Model):
|
||||
class Project(models.Model):
|
||||
_inherit = 'project.project'
|
||||
|
||||
portfolio_id = fields.Many2one(
|
||||
'project.portfolio',
|
||||
string='Portfolio'
|
||||
)
|
||||
|
||||
|
||||
class ProjectPortfolioRoleAssignment(models.Model):
|
||||
_name = 'project.portfolio.role.assignment'
|
||||
_description = 'Project Portfolio Role Assignment'
|
||||
_order = 'role_id'
|
||||
|
||||
portfolio_id = fields.Many2one(
|
||||
'project.portfolio',
|
||||
string='Portfolio',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
)
|
||||
role_id = fields.Many2one(
|
||||
'project.role',
|
||||
string='Role',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
)
|
||||
master_user_ids = fields.Many2many(
|
||||
'res.users',
|
||||
related='role_id.user_ids',
|
||||
string='Master Assigned Users',
|
||||
readonly=True,
|
||||
)
|
||||
user_ids = fields.Many2many(
|
||||
'res.users',
|
||||
'project_portfolio_role_user_rel',
|
||||
'assignment_id',
|
||||
'user_id',
|
||||
string='Portfolio Users',
|
||||
domain="[('id', 'in', master_user_ids)]",
|
||||
help='Users selected for this role in this portfolio.'
|
||||
)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
'portfolio_role_unique',
|
||||
'unique(portfolio_id, role_id)',
|
||||
'Each role can only be configured once per portfolio.',
|
||||
),
|
||||
]
|
||||
|
||||
@api.onchange('role_id')
|
||||
def _onchange_role_id(self):
|
||||
for assignment in self:
|
||||
assignment.user_ids &= assignment.role_id.user_ids
|
||||
|
||||
@api.constrains('role_id', 'user_ids')
|
||||
def _check_user_ids_in_role_master(self):
|
||||
for assignment in self:
|
||||
if assignment.user_ids - assignment.role_id.user_ids:
|
||||
raise ValidationError(_(
|
||||
'Portfolio users must be selected from the users assigned on the role master.'
|
||||
))
|
||||
|
||||
|
||||
class ProjectPortfolioEmployeePerformance(models.Model):
|
||||
_name = 'project.portfolio.employee.performance'
|
||||
_description = 'Project Portfolio Employee Performance'
|
||||
_description = 'Project Portfolio Employee Performance'
|
||||
_order = 'time_variance_percent desc'
|
||||
|
||||
portfolio_id = fields.Many2one(
|
||||
|
|
@ -633,4 +702,4 @@ class ProjectPortfolioEmployeePerformance(models.Model):
|
|||
@api.depends('total_estimated_hours', 'total_actual_hours')
|
||||
def _compute_time_variance(self):
|
||||
for record in self:
|
||||
record.time_variance = record.total_actual_hours - record.total_estimated_hours
|
||||
record.time_variance = record.total_actual_hours - record.total_estimated_hours
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ class TaskStages(models.Model):
|
|||
string='Team Users',
|
||||
compute='_compute_team_related_user_ids',
|
||||
)
|
||||
allowed_internal_team_ids = fields.Many2many(
|
||||
'internal.teams',
|
||||
compute='_compute_allowed_internal_team_ids',
|
||||
string='Allowed Internal Teams',
|
||||
)
|
||||
is_workflow_template = fields.Boolean(
|
||||
string='Workflow Template Stage',
|
||||
default=False,
|
||||
|
|
@ -42,6 +47,22 @@ class TaskStages(models.Model):
|
|||
for rec in self:
|
||||
rec.team_related_user_ids = rec.team_id.all_members_ids | rec.team_id.team_lead
|
||||
|
||||
@api.depends('project_ids', 'project_ids.portfolio_id', 'project_ids.portfolio_id.internal_team_ids')
|
||||
@api.depends_context('project_stage_project_id', 'default_project_id')
|
||||
def _compute_allowed_internal_team_ids(self):
|
||||
Team = self.env['internal.teams']
|
||||
fallback_teams = Team.search([('active', '=', True)])
|
||||
context_project = self.env['project.project'].browse(
|
||||
self.env.context.get('project_stage_project_id') or self.env.context.get('default_project_id')
|
||||
)
|
||||
for rec in self:
|
||||
project = context_project or rec.project_ids[:1]
|
||||
rec.allowed_internal_team_ids = (
|
||||
project.portfolio_id.internal_team_ids
|
||||
if project and project.portfolio_id.internal_team_ids
|
||||
else fallback_teams
|
||||
)
|
||||
|
||||
def _is_project_owned_stage(self, project):
|
||||
self.ensure_one()
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ access_project_cancel_hold_wizard_supervisor,access.project.cancel.hold.wizard.s
|
|||
access_project_cancel_hold_wizard_manager,access.project.cancel.hold.wizard.manager,model_project_cancel_hold_wizard,project.group_project_manager,1,1,1,1
|
||||
|
||||
access_project_role_user,project.role.user,model_project_role,base.group_user,1,0,0,0
|
||||
access_project_portfolio_role_assignment_user,project.portfolio.role.assignment.user,model_project_portfolio_role_assignment,base.group_user,1,1,1,0
|
||||
|
||||
access_project_portfolio_user,project.portfolio.user,model_project_portfolio,base.group_user,1,1,1,0
|
||||
access_project_portfolio_manager,project.portfolio.manager,model_project_portfolio,project.group_project_manager,1,1,1,1
|
||||
access_project_portfolio_role_assignment_manager,project.portfolio.role.assignment.manager,model_project_portfolio_role_assignment,project.group_project_manager,1,1,1,1
|
||||
|
||||
access_project_role_manager,project.role.manager,model_project_role,project.group_project_manager,1,1,1,1
|
||||
|
||||
|
|
|
|||
|
|
|
@ -17,12 +17,18 @@
|
|||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='user_id']" position="after">
|
||||
<field name="project_lead" widget="many2many_avatar_user"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='user_id']" position="replace">
|
||||
<field name="user_id" widget="user_timeline"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='user_id']" position="after">
|
||||
<field name="project_lead"
|
||||
widget="many2many_avatar_user"
|
||||
domain="[('id', 'in', allowed_project_lead_user_ids)]"
|
||||
options="{'no_create': True, 'no_create_edit': True, 'no_quick_create': True}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='user_id']" position="replace">
|
||||
<field name="user_id"
|
||||
widget="user_timeline"
|
||||
domain="[('id', 'in', allowed_project_manager_user_ids)]"
|
||||
options="{'no_create': True, 'no_create_edit': True, 'no_quick_create': True}"/>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='settings']" position="inside">
|
||||
<group>
|
||||
<group name="group_project_channel_managment" string="Project Channel" col="1"
|
||||
|
|
@ -85,6 +91,9 @@
|
|||
<field name="enable_budget_summary" invisible="1"/>
|
||||
<field name="can_edit_stage_in_approval" invisible="1"/>
|
||||
<field name="active_sprint_id" invisible="1"/>
|
||||
<field name="allowed_project_manager_user_ids" invisible="1"/>
|
||||
<field name="allowed_project_lead_user_ids" invisible="1"/>
|
||||
<field name="allowed_internal_team_ids" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<button class="oe_stat_button" type="object" name="action_open_sprint_backlog"
|
||||
|
|
@ -130,7 +139,10 @@
|
|||
<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="team_id"
|
||||
string="Assigned Team"
|
||||
domain="[('id', 'in', parent.allowed_internal_team_ids)]"
|
||||
options="{'no_create': True, 'no_create_edit': True, 'no_quick_create': True}"/>
|
||||
<field name="responsible_user_id" string="Responsible User" readonly="1"/>
|
||||
<field name="approval_by" string="Approval Owner"/>
|
||||
<field name="fold"/>
|
||||
|
|
|
|||
|
|
@ -144,10 +144,33 @@
|
|||
</group>
|
||||
</group>
|
||||
|
||||
<notebook>
|
||||
<page string="Projects" invisible="project_count == 0">
|
||||
<field name="project_ids">
|
||||
<list>
|
||||
<notebook>
|
||||
<page string="Internal Teams">
|
||||
<field name="internal_team_ids"
|
||||
widget="many2many_tags"
|
||||
options="{'no_create': True, 'no_create_edit': True, 'no_quick_create': True}"/>
|
||||
</page>
|
||||
|
||||
<page string="Portfolio Roles">
|
||||
<field name="role_assignment_ids">
|
||||
<list editable="bottom">
|
||||
<field name="role_id"
|
||||
options="{'no_create': True, 'no_create_edit': True, 'no_quick_create': True}"/>
|
||||
<field name="master_user_ids"
|
||||
widget="many2many_tags"
|
||||
readonly="1"
|
||||
optional="show"/>
|
||||
<field name="user_ids"
|
||||
widget="many2many_tags"
|
||||
domain="[('id', 'in', master_user_ids)]"
|
||||
options="{'no_create': True, 'no_create_edit': True, 'no_quick_create': True}"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Projects" invisible="project_count == 0">
|
||||
<field name="project_ids">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="user_id" widget="user_timeline"/>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@
|
|||
<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="allowed_internal_team_ids" invisible="1"/>
|
||||
<field name="team_id"
|
||||
domain="[('id', 'in', allowed_internal_team_ids)]"
|
||||
options="{'no_create': True, 'no_create_edit': True, 'no_quick_create': True}"/>
|
||||
<field name="responsible_user_id"/>
|
||||
<field name="approval_by"/>
|
||||
<field name="team_related_user_ids" invisible="1"/>
|
||||
|
|
@ -22,7 +25,11 @@
|
|||
<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="allowed_internal_team_ids" invisible="1"/>
|
||||
<field name="team_id"
|
||||
optional="show"
|
||||
domain="[('id', 'in', allowed_internal_team_ids)]"
|
||||
options="{'no_create': True, 'no_create_edit': True, 'no_quick_create': True}"/>
|
||||
<field name="responsible_user_id" optional="show"/>
|
||||
<field name="approval_by" optional="show"/>
|
||||
<field name="team_related_user_ids" invisible="1"/>
|
||||
|
|
|
|||
Loading…
Reference in New Issue