671 lines
25 KiB
Python
671 lines
25 KiB
Python
|
# Part of Flectra. See LICENSE file for full copyright and licensing details.
|
||
|
|
||
|
from flectra import models, fields, api, _
|
||
|
from flectra.exceptions import ValidationError
|
||
|
from datetime import datetime, timedelta
|
||
|
|
||
|
|
||
|
class ProjectSprint(models.Model):
|
||
|
_name = "project.sprint"
|
||
|
_inherit = ['ir.branch.company.mixin', 'mail.thread']
|
||
|
_description = "Sprint of the Project"
|
||
|
_rec_name = 'sprint_seq'
|
||
|
|
||
|
sprint_seq = fields.Char(
|
||
|
string="Reference", readonly=True)
|
||
|
name = fields.Char("Sprint Name", required=True,
|
||
|
track_visibility="onchange")
|
||
|
goal_of_sprint = fields.Char("Goal of Sprint", track_visibility="onchange")
|
||
|
|
||
|
meeting_date = fields.Datetime("Planning Meeting Date", required=True,
|
||
|
track_visibility="onchange")
|
||
|
hour = fields.Float(string="Hour", track_visibility="onchange")
|
||
|
time_zone = fields.Selection([
|
||
|
('am', 'AM'),
|
||
|
('pm', 'PM'),
|
||
|
], track_visibility="onchange")
|
||
|
estimated_velocity = fields.Integer(
|
||
|
compute="calculate_estimated_velocity", string="Estimated Velocity",
|
||
|
store=True, track_visibility="onchange")
|
||
|
actual_velocity = fields.Integer(
|
||
|
compute="calculate_actual_velocity", string="Actual Velocity",
|
||
|
store=True, track_visibility="onchange")
|
||
|
sprint_planning_line = fields.One2many(
|
||
|
'sprint.planning.line', 'sprint_id', string="Sprint Planning Lines")
|
||
|
project_id = fields.Many2one('project.project', string="Project",
|
||
|
track_visibility="onchange")
|
||
|
start_date = fields.Date(string="Start Date", track_visibility="onchange")
|
||
|
end_date = fields.Date(string="End Date", track_visibility="onchange")
|
||
|
working_days = fields.Integer(
|
||
|
compute="calculate_business_days", string="Business Days",
|
||
|
store=True, track_visibility="onchange")
|
||
|
productivity_hours = fields.Float(string="Productivity Hours",
|
||
|
track_visibility="onchange")
|
||
|
productivity_per = fields.Float(
|
||
|
compute="calculate_productivity_per", string="Productivity (%)",
|
||
|
store=True, track_visibility="onchange")
|
||
|
holiday_type = fields.Selection(
|
||
|
[('hours', 'Hours'), ('days', 'Days')],
|
||
|
string="Holiday (Hours / Days)", default='hours',
|
||
|
track_visibility="onchange")
|
||
|
holiday_count = fields.Float(string="Holiday Count",
|
||
|
track_visibility="onchange")
|
||
|
holiday_days = fields.Float(
|
||
|
compute="calculate_holiday_days", string="Holiday Days", store=True,
|
||
|
track_visibility="onchange")
|
||
|
state = fields.Selection([
|
||
|
('draft', 'Draft'),
|
||
|
('in_progress', 'In Progress'),
|
||
|
('pending', 'Pending'),
|
||
|
('done', 'Done'),
|
||
|
('cancel', 'Cancel')], string="State", default='draft',
|
||
|
track_visibility="onchange")
|
||
|
team_id = fields.Many2one('project.team', string="Team",
|
||
|
track_visibility="onchange", required=True)
|
||
|
task_line = fields.One2many('project.task', 'sprint_id', string='Tasks')
|
||
|
color = fields.Integer('Color Index')
|
||
|
|
||
|
@api.depends('start_date', 'end_date')
|
||
|
def days_calculate(self):
|
||
|
if self.start_date and self.end_date:
|
||
|
diff = datetime.strptime(
|
||
|
self.end_date, '%Y-%m-%d').date() - datetime.strptime(
|
||
|
self.start_date, '%Y-%m-%d').date()
|
||
|
self.duration = diff.days
|
||
|
|
||
|
duration = fields.Integer(
|
||
|
"Duration (In Days)", compute='days_calculate', store=True,
|
||
|
track_visibility="onchange")
|
||
|
|
||
|
@api.multi
|
||
|
def _get_task_count(self):
|
||
|
for record in self:
|
||
|
record.number_of_tasks = record.env['project.task'].search_count([
|
||
|
('sprint_id', '=', record.id)])
|
||
|
|
||
|
number_of_tasks = fields.Integer(
|
||
|
string="# of tasks", compute="_get_task_count")
|
||
|
|
||
|
@api.multi
|
||
|
def _get_story_count(self):
|
||
|
for record in self:
|
||
|
count = record.env['project.story'].search_count([
|
||
|
('sprint_id', '=', record.id)])
|
||
|
record.number_of_stories = count
|
||
|
|
||
|
number_of_stories = fields.Integer(
|
||
|
string="# of stories", compute="_get_story_count")
|
||
|
|
||
|
@api.multi
|
||
|
def _get_retrospective_count(self):
|
||
|
for record in self:
|
||
|
count = record.env['retrospective'].search_count([
|
||
|
('sprint_id', '=', record.id)])
|
||
|
record.number_of_retrospectives = count
|
||
|
|
||
|
number_of_retrospectives = fields.Integer(
|
||
|
string="# of Retrospectives", compute="_get_retrospective_count")
|
||
|
|
||
|
@api.multi
|
||
|
@api.depends('task_line', 'task_line.stage_id', 'task_line.sprint_id',
|
||
|
'estimated_velocity', 'start_date', 'end_date', 'project_id',
|
||
|
'team_id')
|
||
|
def calculate_tasks_json(self):
|
||
|
data = []
|
||
|
for record in self:
|
||
|
task_ids = self.env['project.task'].search([
|
||
|
('sprint_id', '=', record.id)])
|
||
|
velocity = record.estimated_velocity or 1.0
|
||
|
for task in task_ids:
|
||
|
data.append({
|
||
|
'task': task.task_seq or '/',
|
||
|
'velocity': task.velocity or 0,
|
||
|
'per': round(((float(task.velocity) * 100) / float(
|
||
|
velocity)), 2)
|
||
|
})
|
||
|
record.tasks_json = data
|
||
|
|
||
|
tasks_json = fields.Char(
|
||
|
string="Tasks", compute="calculate_tasks_json", store=True)
|
||
|
|
||
|
@api.multi
|
||
|
def get_data(self):
|
||
|
task_dict_list = []
|
||
|
for record in self:
|
||
|
task_pool = self.env['project.task'].search([
|
||
|
('sprint_id', '=', record.id)])
|
||
|
for task in task_pool:
|
||
|
task_dict = {
|
||
|
'reference': task.task_seq,
|
||
|
'name': task.name,
|
||
|
'velocity': task.velocity,
|
||
|
'start_date': task.start_date,
|
||
|
'end_date': task.end_date,
|
||
|
'actual_end_date': task.actual_end_date,
|
||
|
'assigned_to': task.user_id.name,
|
||
|
'state': task.stage_id.name,
|
||
|
}
|
||
|
task_dict_list.append(task_dict)
|
||
|
return task_dict_list
|
||
|
|
||
|
@api.multi
|
||
|
def set_state_open(self):
|
||
|
self.state = 'in_progress'
|
||
|
|
||
|
@api.multi
|
||
|
def set_state_cancel(self):
|
||
|
self.state = 'cancel'
|
||
|
|
||
|
@api.multi
|
||
|
def set_state_pending(self):
|
||
|
self.state = 'pending'
|
||
|
|
||
|
@api.multi
|
||
|
def redirect_to_view(self, model, caption):
|
||
|
return {
|
||
|
'name': (_(caption)),
|
||
|
'type': 'ir.actions.act_window',
|
||
|
'view_type': 'form',
|
||
|
'view_mode': 'tree,form',
|
||
|
'res_model': model,
|
||
|
'domain': [('sprint_id', '=', self.id)],
|
||
|
'context': {
|
||
|
'default_sprint_id': self.id,
|
||
|
'default_project_id': self.project_id.id
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@api.multi
|
||
|
def action_view_tasks(self):
|
||
|
return self.redirect_to_view("project.task", "Tasks")
|
||
|
|
||
|
@api.multi
|
||
|
def action_view_stories(self):
|
||
|
return self.redirect_to_view("project.story", "Stories")
|
||
|
|
||
|
@api.multi
|
||
|
def action_view_release_planning(self):
|
||
|
return self.redirect_to_view("release.planning", "Release Planning")
|
||
|
|
||
|
@api.multi
|
||
|
def action_view_retrospective(self):
|
||
|
return self.redirect_to_view("retrospective", "Retrospective")
|
||
|
|
||
|
@api.constrains('start_date', 'end_date')
|
||
|
def check_dates(self):
|
||
|
if self.start_date and self.end_date and (
|
||
|
self.start_date > self.end_date):
|
||
|
raise ValidationError(
|
||
|
"Start Date can not be greater than End date, Dude!")
|
||
|
|
||
|
@api.onchange('holiday_type')
|
||
|
def onchange_holiday_type(self):
|
||
|
self.holiday_count = 0.0
|
||
|
self.holiday_days = 0.0
|
||
|
|
||
|
@api.multi
|
||
|
@api.depends('project_id', 'project_id.no_of_days', 'start_date',
|
||
|
'end_date')
|
||
|
def calculate_business_days(self):
|
||
|
for record in self:
|
||
|
if record.start_date and record.end_date:
|
||
|
days_dict = {
|
||
|
0: (1, 2, 3, 4, 5, 6, 7),
|
||
|
1: (2, 3, 4, 5, 6, 7),
|
||
|
2: (3, 4, 5, 6, 7),
|
||
|
3: (4, 5, 6, 7),
|
||
|
4: (5, 6, 7),
|
||
|
5: (6, 7),
|
||
|
6: (7,),
|
||
|
7: (),
|
||
|
}
|
||
|
start = datetime.strptime(record.start_date, "%Y-%m-%d").date()
|
||
|
end = datetime.strptime(record.end_date, "%Y-%m-%d").date()
|
||
|
delta = timedelta(days=1)
|
||
|
days = 0
|
||
|
|
||
|
if record.project_id and end > start:
|
||
|
working_days = record.project_id.no_of_days
|
||
|
non_working_days = days_dict[working_days]
|
||
|
|
||
|
while end != start:
|
||
|
end -= delta
|
||
|
if end.isoweekday() not in non_working_days:
|
||
|
days += 1
|
||
|
|
||
|
record.working_days = days
|
||
|
|
||
|
@api.multi
|
||
|
@api.depends('project_id', 'project_id.no_of_hours', 'productivity_hours')
|
||
|
def calculate_productivity_per(self):
|
||
|
for record in self:
|
||
|
project_id = record.project_id
|
||
|
no_of_hours = project_id.no_of_hours if project_id else 0
|
||
|
prod_hours = record.productivity_hours
|
||
|
if project_id and no_of_hours > 0 and prod_hours:
|
||
|
record.productivity_per = (prod_hours / no_of_hours) * 100
|
||
|
|
||
|
@api.multi
|
||
|
@api.depends('project_id', 'project_id.no_of_hours', 'holiday_count')
|
||
|
def calculate_holiday_days(self):
|
||
|
for record in self:
|
||
|
if record.holiday_type == 'days' and record.project_id:
|
||
|
hours = record.holiday_count * record.project_id.no_of_hours
|
||
|
record.holiday_days = hours
|
||
|
|
||
|
@api.multi
|
||
|
@api.depends('project_id', 'task_line', 'task_line.velocity')
|
||
|
def calculate_estimated_velocity(self):
|
||
|
for record in self:
|
||
|
task_ids = record.env['project.task'].search([
|
||
|
('sprint_id', '=', record.id)
|
||
|
])
|
||
|
total_velocity = sum([
|
||
|
task.velocity for task in task_ids if task.velocity])
|
||
|
record.estimated_velocity = total_velocity
|
||
|
|
||
|
@api.multi
|
||
|
@api.depends('project_id', 'end_date', 'task_line', 'task_line.velocity',
|
||
|
'task_line.stage_id')
|
||
|
def calculate_actual_velocity(self):
|
||
|
for record in self:
|
||
|
task_ids = record.env['project.task'].search([
|
||
|
('sprint_id', '=', record.id),
|
||
|
('actual_end_date', '<=', record.end_date),
|
||
|
])
|
||
|
total_velocity = sum([
|
||
|
task.velocity for task in task_ids if task.velocity])
|
||
|
record.actual_velocity = total_velocity
|
||
|
|
||
|
@api.onchange('duration')
|
||
|
def onchange_start_date(self):
|
||
|
if self.start_date:
|
||
|
end_date = datetime.strptime(
|
||
|
self.start_date, '%Y-%m-%d') + timedelta(days=self.duration)
|
||
|
self.end_date = end_date
|
||
|
|
||
|
@api.onchange('project_id')
|
||
|
def onchange_project(self):
|
||
|
if self.project_id and self.project_id.branch_id:
|
||
|
self.branch_id = self.project_id.branch_id
|
||
|
|
||
|
@api.multi
|
||
|
def check_sprint_state(self):
|
||
|
next_call = datetime.today()
|
||
|
for record in self.search([('state', '!=', 'done')]):
|
||
|
if record.end_date:
|
||
|
end_date = datetime.strptime(
|
||
|
record.end_date, '%Y-%m-%d').date()
|
||
|
if end_date < next_call:
|
||
|
record.state = 'done'
|
||
|
|
||
|
@api.constrains('sprint_planning_line')
|
||
|
def check_users_in_planning_line(self):
|
||
|
user_list = []
|
||
|
for user in self.sprint_planning_line:
|
||
|
if user.user_id.id not in user_list:
|
||
|
user_list.append(user.user_id.id)
|
||
|
else:
|
||
|
raise ValidationError(
|
||
|
"You can't add the same user twice in Sprint Planning!")
|
||
|
|
||
|
@api.model
|
||
|
def create(self, vals):
|
||
|
vals['sprint_seq'] = self.env[
|
||
|
'ir.sequence'].next_by_code('project.sprint')
|
||
|
|
||
|
res = super(ProjectSprint, self).create(vals)
|
||
|
partner_list = []
|
||
|
|
||
|
mail_channel_id = self.env['mail.channel'].sudo().search([
|
||
|
('name', '=', 'Project Sprint')
|
||
|
])
|
||
|
if mail_channel_id:
|
||
|
mail_channel_ids = self.env['mail.followers'].sudo().search([
|
||
|
('channel_id', '=', mail_channel_id.id),
|
||
|
('res_model', '=', res._name),
|
||
|
('res_id', '=', res.id),
|
||
|
])
|
||
|
if not mail_channel_ids:
|
||
|
self.env['mail.followers'].sudo().create({
|
||
|
'channel_id': mail_channel_id.id,
|
||
|
'res_model': res._name,
|
||
|
'res_id': res.id,
|
||
|
})
|
||
|
|
||
|
if 'team_id' in vals:
|
||
|
team_id = self.env['project.team'].browse(vals['team_id'])
|
||
|
partner_list += [member.partner_id.id
|
||
|
for member in team_id.member_ids]
|
||
|
|
||
|
for follower in partner_list:
|
||
|
if follower:
|
||
|
mail_follower_ids = self.env['mail.followers'].sudo().search([
|
||
|
('res_id', '=', res.id),
|
||
|
('partner_id', '=', follower),
|
||
|
('res_model', '=', res._name),
|
||
|
])
|
||
|
if not mail_follower_ids:
|
||
|
self.env['mail.followers'].sudo().create({
|
||
|
'res_id': res.id,
|
||
|
'res_model': res._name,
|
||
|
'partner_id': follower,
|
||
|
'team_id': team_id.id,
|
||
|
})
|
||
|
return res
|
||
|
|
||
|
@api.multi
|
||
|
def write(self, vals):
|
||
|
res = super(ProjectSprint, self).write(vals)
|
||
|
|
||
|
if 'team_id' in vals:
|
||
|
team_id = self.env['project.team'].browse(vals['team_id'])
|
||
|
else:
|
||
|
team_id = self.team_id
|
||
|
|
||
|
delete_team_id = self.env['mail.followers'].sudo().search([
|
||
|
('team_id', '!=', team_id.id),
|
||
|
('res_id', '=', self.id),
|
||
|
])
|
||
|
delete_team_id.unlink()
|
||
|
|
||
|
partner_list = [member.partner_id.id for member in team_id.member_ids]
|
||
|
for follower in partner_list:
|
||
|
if follower:
|
||
|
mail_follower_ids = self.env['mail.followers'].sudo().search([
|
||
|
('res_id', '=', self.id),
|
||
|
('partner_id', '=', follower),
|
||
|
('res_model', '=', self._name),
|
||
|
])
|
||
|
if not mail_follower_ids:
|
||
|
self.env['mail.followers'].sudo().create({
|
||
|
'res_id': self.id,
|
||
|
'res_model': self._name,
|
||
|
'partner_id': follower,
|
||
|
'team_id': team_id.id,
|
||
|
})
|
||
|
return res
|
||
|
|
||
|
@api.multi
|
||
|
def _track_subtype(self, init_values):
|
||
|
self.ensure_one()
|
||
|
if 'state' in init_values and self.state == 'draft':
|
||
|
return 'project_scrum.state_sprint_draft'
|
||
|
elif 'state' in init_values and self.state == 'in_progress':
|
||
|
return 'project_scrum.state_sprint_in_progress'
|
||
|
elif 'state' in init_values and self.state == 'pending':
|
||
|
return 'project_scrum.state_sprint_pending'
|
||
|
elif 'state' in init_values and self.state == 'done':
|
||
|
return 'project_scrum.state_sprint_done'
|
||
|
elif 'state' in init_values and self.state == 'cancel':
|
||
|
return 'project_scrum.state_sprint_cancel'
|
||
|
return super(ProjectSprint, self)._track_subtype(init_values)
|
||
|
|
||
|
|
||
|
class Project(models.Model):
|
||
|
_inherit = "project.project"
|
||
|
|
||
|
@api.multi
|
||
|
def _sprint_count(self):
|
||
|
for project in self:
|
||
|
count = project.env['project.sprint'].search_count([
|
||
|
('project_id', '=', project.id)
|
||
|
])
|
||
|
project.sprint_count = count
|
||
|
|
||
|
sprint_count = fields.Integer(
|
||
|
compute="_sprint_count", string="No. of sprints related to")
|
||
|
|
||
|
@api.multi
|
||
|
def show_sprints(self):
|
||
|
self.ensure_one()
|
||
|
return {
|
||
|
'name': ("Sprints"),
|
||
|
'type': 'ir.actions.act_window',
|
||
|
'view_type': 'form',
|
||
|
'view_mode': 'tree,form',
|
||
|
'res_model': 'project.sprint',
|
||
|
'domain': [('project_id', '=', self.id)]
|
||
|
}
|
||
|
|
||
|
no_of_hours = fields.Integer(
|
||
|
related="resource_calendar_id.no_of_hours",
|
||
|
string="Working Hour(s) per Day", store=True)
|
||
|
no_of_days = fields.Integer(
|
||
|
related="resource_calendar_id.no_of_days",
|
||
|
string="Working Day(s) per Week", store=True)
|
||
|
task_type_ids = fields.Many2many(
|
||
|
'project.task.type', 'project_task_type_rel', 'project_id', 'type_id',
|
||
|
string='Project(s)')
|
||
|
|
||
|
|
||
|
class SprintPlanningLine(models.Model):
|
||
|
_name = "sprint.planning.line"
|
||
|
|
||
|
@api.multi
|
||
|
@api.depends('available_per', 'sprint_id.project_id',
|
||
|
'sprint_id.project_id.no_of_hours',
|
||
|
'sprint_id', 'sprint_id.working_days')
|
||
|
def calculate_hours(self):
|
||
|
for record in self:
|
||
|
project_id = record.sprint_id.project_id
|
||
|
no_of_hours = project_id and project_id.no_of_hours or 0.0
|
||
|
calc = (
|
||
|
record.available_per *
|
||
|
record.sprint_id.working_days * no_of_hours) / 100
|
||
|
record.productivity_hours = float(calc)
|
||
|
|
||
|
@api.multi
|
||
|
@api.depends('sprint_id', 'available_per', 'sprint_id.working_days',
|
||
|
'sprint_id.productivity_hours')
|
||
|
def calculate_sprint_hours(self):
|
||
|
for record in self:
|
||
|
hours = (record.available_per * record.sprint_id.working_days *
|
||
|
record.sprint_id.productivity_hours) / 100
|
||
|
record.sprint_hours = hours
|
||
|
|
||
|
@api.multi
|
||
|
@api.depends('sprint_id', 'sprint_id.holiday_count',
|
||
|
'sprint_id.holiday_days', 'sprint_hours', 'off_hours')
|
||
|
def calculate_total_hours(self):
|
||
|
for record in self:
|
||
|
if record.sprint_id.holiday_type == 'hours':
|
||
|
hours = (
|
||
|
record.sprint_hours - record.sprint_id.holiday_count -
|
||
|
record.off_hours)
|
||
|
else:
|
||
|
hours = (
|
||
|
record.sprint_hours - record.sprint_id.holiday_days -
|
||
|
record.off_hours)
|
||
|
record.total_hours = hours
|
||
|
|
||
|
sprint_id = fields.Many2one('project.sprint', string="Sprint")
|
||
|
user_id = fields.Many2one('res.users', string="User")
|
||
|
role_id = fields.Many2one(
|
||
|
related="user_id.role_id", string="Role", store=True)
|
||
|
available_per = fields.Integer(string="Available (%)")
|
||
|
productivity_hours = fields.Float(
|
||
|
compute="calculate_hours", string="Productivity Hour(s)", store=True)
|
||
|
sprint_hours = fields.Float(
|
||
|
compute="calculate_sprint_hours", string="Sprint Hour(s)", store=True)
|
||
|
off_hours = fields.Float(string="Off Hour(s)")
|
||
|
total_hours = fields.Float(
|
||
|
compute="calculate_total_hours", string="Total Hour(s)", store=True)
|
||
|
|
||
|
|
||
|
class UserRole(models.Model):
|
||
|
_name = "user.role"
|
||
|
|
||
|
name = fields.Char(string="Role")
|
||
|
code = fields.Char(string="Code")
|
||
|
|
||
|
|
||
|
class ResUsers(models.Model):
|
||
|
_inherit = "res.users"
|
||
|
|
||
|
role_id = fields.Many2one("user.role", string="User Role")
|
||
|
|
||
|
|
||
|
class ProjectTask(models.Model):
|
||
|
_inherit = "project.task"
|
||
|
|
||
|
sprint_id = fields.Many2one(
|
||
|
'project.sprint', string="Sprint", track_visibility="onchange")
|
||
|
velocity = fields.Integer(string="Velocity", track_visibility="onchange")
|
||
|
story_id = fields.Many2one(
|
||
|
'project.story', string="Story", track_visibility="onchange")
|
||
|
release_planning_id = fields.Many2one(
|
||
|
"release.planning", string="Release Planning",
|
||
|
track_visibility="onchange")
|
||
|
|
||
|
@api.onchange('story_id')
|
||
|
def onchange_story(self):
|
||
|
if self.story_id:
|
||
|
self.description = self.story_id.description
|
||
|
|
||
|
@api.constrains('start_date', 'end_date')
|
||
|
def check_dates(self):
|
||
|
if self.sprint_id:
|
||
|
start_date = self.sprint_id.start_date
|
||
|
end_date = self.sprint_id.end_date
|
||
|
|
||
|
if self.start_date and start_date and (
|
||
|
self.start_date < start_date):
|
||
|
raise ValidationError(
|
||
|
"Start date is not valid according to the Sprint.")
|
||
|
|
||
|
if self.end_date and end_date and (self.end_date > end_date):
|
||
|
raise ValidationError(
|
||
|
"End date is not valid according to the Sprint.")
|
||
|
|
||
|
@api.model
|
||
|
def create(self, vals):
|
||
|
res = super(ProjectTask, self).create(vals)
|
||
|
|
||
|
partner_list = [
|
||
|
member.partner_id.id
|
||
|
for member in res.sprint_id.team_id.member_ids
|
||
|
]
|
||
|
|
||
|
for follower in partner_list:
|
||
|
if follower:
|
||
|
mail_follower_ids = self.env['mail.followers'].sudo().search([
|
||
|
('res_id', '=', res.id),
|
||
|
('partner_id', '=', follower),
|
||
|
('res_model', '=', self._name),
|
||
|
])
|
||
|
if not mail_follower_ids:
|
||
|
self.env['mail.followers'].sudo().create({
|
||
|
'res_id': res.id,
|
||
|
'res_model': self._name,
|
||
|
'partner_id': follower,
|
||
|
'team_id': res.sprint_id.team_id.id,
|
||
|
})
|
||
|
return res
|
||
|
|
||
|
@api.multi
|
||
|
def write(self, vals):
|
||
|
if self.task_seq == '/':
|
||
|
vals['task_seq'] = self.env['ir.sequence'].next_by_code(
|
||
|
'project.task')
|
||
|
res = super(ProjectTask, self).write(vals)
|
||
|
|
||
|
data = []
|
||
|
for record in self:
|
||
|
task_ids = self.search([
|
||
|
('sprint_id', '=', record.sprint_id.id)])
|
||
|
for task in task_ids:
|
||
|
data.append({
|
||
|
'task': task.task_seq or '/',
|
||
|
'velocity': task.velocity or 0,
|
||
|
'per': round(((float(task.velocity) * 100) / float(
|
||
|
record.sprint_id.estimated_velocity)), 2)
|
||
|
if record.sprint_id.estimated_velocity > 0 else 0
|
||
|
})
|
||
|
|
||
|
record.sprint_id.write({'tasks_json': data})
|
||
|
|
||
|
if 'sprint_id' in vals:
|
||
|
sprint_id = self.env['project.sprint'].browse(vals['sprint_id'])
|
||
|
team_id = sprint_id.team_id
|
||
|
else:
|
||
|
team_id = self.sprint_id.team_id
|
||
|
|
||
|
delete_team_id = self.env['mail.followers'].sudo().search([
|
||
|
('team_id', '!=', team_id.id),
|
||
|
('res_id', '=', self.id),
|
||
|
])
|
||
|
delete_team_id.unlink()
|
||
|
|
||
|
partner_list = [member.partner_id.id for member in team_id.member_ids]
|
||
|
for follower in partner_list:
|
||
|
if follower:
|
||
|
mail_follower_ids = self.env['mail.followers'].sudo().search([
|
||
|
('res_id', '=', self.id),
|
||
|
('partner_id', '=', follower),
|
||
|
('res_model', '=', self._name),
|
||
|
])
|
||
|
if not mail_follower_ids:
|
||
|
self.env['mail.followers'].sudo().create({
|
||
|
'res_id': self.id,
|
||
|
'res_model': self._name,
|
||
|
'partner_id': follower,
|
||
|
'team_id': team_id.id,
|
||
|
})
|
||
|
return res
|
||
|
|
||
|
|
||
|
class MailFollowers(models.Model):
|
||
|
_inherit = "mail.followers"
|
||
|
|
||
|
team_id = fields.Many2one("project.team", string="Project Team")
|
||
|
|
||
|
|
||
|
class ResourceCalendar(models.Model):
|
||
|
_inherit = "resource.calendar"
|
||
|
|
||
|
no_of_hours = fields.Integer(string="No. of Hour(s) a Day", default=8)
|
||
|
no_of_days = fields.Integer(string="No. of Day(s) a Week", default=5)
|
||
|
|
||
|
@api.multi
|
||
|
@api.constrains('no_of_hours', 'no_of_days', 'attendance_ids')
|
||
|
def _check_days_hours(self):
|
||
|
week_days = {
|
||
|
0: 'Monday',
|
||
|
1: 'Tuesday',
|
||
|
2: 'Wednesday',
|
||
|
3: 'Thursday',
|
||
|
4: 'Friday',
|
||
|
5: 'Saturday',
|
||
|
6: 'Sunday'
|
||
|
}
|
||
|
|
||
|
for resource in self:
|
||
|
res = {}
|
||
|
current_hours = resource.no_of_hours
|
||
|
current_days = resource.no_of_days
|
||
|
|
||
|
if current_days > 7 or current_days < 1:
|
||
|
raise ValidationError(_(
|
||
|
"No. of Days should be in between 1 - 7."))
|
||
|
|
||
|
for attendance in resource.attendance_ids:
|
||
|
day = attendance.dayofweek
|
||
|
diff = abs(attendance.hour_from - attendance.hour_to)
|
||
|
if day not in res:
|
||
|
res[day] = diff
|
||
|
else:
|
||
|
res[day] += diff
|
||
|
|
||
|
if current_days and len(res) > current_days:
|
||
|
raise ValidationError(_(
|
||
|
"You can not Add Working Hour(s) for more than %s\
|
||
|
different days.") % current_days)
|
||
|
|
||
|
for day, hours in sorted(list(res.items())):
|
||
|
if current_hours and hours > current_hours:
|
||
|
raise ValidationError(_(
|
||
|
"Invalid hours for %s!\n\nWorking hours per day should\
|
||
|
not be greater than %s.") % (
|
||
|
week_days[int(day)], current_hours))
|