project task timesheets sprints and kanban buttons
This commit is contained in:
parent
fd866fb866
commit
e5e876ecb4
|
|
@ -238,6 +238,27 @@ class ProjectProject(models.Model):
|
|||
"project_id",
|
||||
string="Project Sprints"
|
||||
)
|
||||
active_sprint_id = fields.Many2one(
|
||||
"project.sprint",
|
||||
compute="_compute_sprint_planning_metrics",
|
||||
string="Active Sprint",
|
||||
)
|
||||
sprint_count = fields.Integer(
|
||||
compute="_compute_sprint_planning_metrics",
|
||||
string="Sprints",
|
||||
)
|
||||
sprint_backlog_task_count = fields.Integer(
|
||||
compute="_compute_sprint_planning_metrics",
|
||||
string="Backlog Tasks",
|
||||
)
|
||||
sprint_planned_hours = fields.Float(
|
||||
compute="_compute_sprint_planning_metrics",
|
||||
string="Sprint Planned Hours",
|
||||
)
|
||||
sprint_actual_hours = fields.Float(
|
||||
compute="_compute_sprint_planning_metrics",
|
||||
string="Sprint Actual Hours",
|
||||
)
|
||||
|
||||
commit_step_ids = fields.One2many(
|
||||
'project.commit.step',
|
||||
|
|
@ -301,6 +322,45 @@ class ProjectProject(models.Model):
|
|||
hold_reason = fields.Text(string="Hold Reason", tracking=True)
|
||||
privacy_visibility = fields.Selection(default="followers")
|
||||
|
||||
@api.depends(
|
||||
"sprint_ids",
|
||||
"sprint_ids.status",
|
||||
"sprint_ids.task_ids",
|
||||
"sprint_ids.task_ids.sprint_estimated_hours",
|
||||
"sprint_ids.task_ids.estimated_hours",
|
||||
"sprint_ids.task_ids.actual_hours",
|
||||
"task_ids.sprint_id",
|
||||
)
|
||||
def _compute_sprint_planning_metrics(self):
|
||||
for project in self:
|
||||
active_sprint = project.sprint_ids.filtered(lambda sprint: sprint.status == "in_progress")[:1]
|
||||
sprint_tasks = project.task_ids.filtered(lambda task: task.sprint_id)
|
||||
project.active_sprint_id = active_sprint
|
||||
project.sprint_count = len(project.sprint_ids)
|
||||
project.sprint_backlog_task_count = len(project.task_ids.filtered(lambda task: not task.sprint_id))
|
||||
project.sprint_planned_hours = sum(task.sprint_estimated_hours or task.estimated_hours for task in sprint_tasks)
|
||||
project.sprint_actual_hours = sum(sprint_tasks.mapped("actual_hours"))
|
||||
|
||||
def action_open_sprint_backlog(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"name": _("Sprint Backlog"),
|
||||
"res_model": "project.task",
|
||||
"view_mode": "kanban,list,form,calendar,pivot,graph,activity",
|
||||
"domain": [("project_id", "=", self.id), ("sprint_id", "=", False)],
|
||||
"context": {
|
||||
"default_project_id": self.id,
|
||||
"search_default_project_id": self.id,
|
||||
},
|
||||
}
|
||||
|
||||
def action_open_active_sprint_tasks(self):
|
||||
self.ensure_one()
|
||||
if not self.active_sprint_id:
|
||||
raise UserError(_("There is no active sprint for this project."))
|
||||
return self.active_sprint_id.action_open_tasks()
|
||||
|
||||
def action_hold_unhold(self):
|
||||
for project in self:
|
||||
if project.project_state == 'hold':
|
||||
|
|
@ -1179,8 +1239,8 @@ class ProjectTask(models.Model):
|
|||
|
||||
def _default_sprint_id(self):
|
||||
"""Return the current active (in-progress) sprint of the project."""
|
||||
if 'project_id' in self._context:
|
||||
project_id = self._context.get('project_id')
|
||||
project_id = self._context.get('default_project_id') or self._context.get('project_id')
|
||||
if project_id:
|
||||
sprint = self.env['project.sprint'].search([
|
||||
('project_id', '=', project_id),
|
||||
('status', '=', 'in_progress')
|
||||
|
|
@ -1191,7 +1251,9 @@ class ProjectTask(models.Model):
|
|||
sprint_id = fields.Many2one(
|
||||
"project.sprint",
|
||||
string="Sprint",
|
||||
default=_default_sprint_id
|
||||
default=_default_sprint_id,
|
||||
tracking=True,
|
||||
index=True,
|
||||
)
|
||||
|
||||
require_sprint = fields.Boolean(
|
||||
|
|
@ -1224,6 +1286,8 @@ class ProjectTask(models.Model):
|
|||
for task in self:
|
||||
if task.project_id and not task.project_id.require_sprint:
|
||||
task.sprint_id = False
|
||||
elif task.sprint_id and task.sprint_id.project_id != task.project_id:
|
||||
task.sprint_id = False
|
||||
else:
|
||||
if task.project_id and task.project_id.require_sprint:
|
||||
sprint = self.env['project.sprint'].search([
|
||||
|
|
@ -1232,10 +1296,35 @@ class ProjectTask(models.Model):
|
|||
], limit=1)
|
||||
task.sprint_id = sprint.id
|
||||
|
||||
@api.onchange("sprint_id")
|
||||
def _onchange_sprint_id_project(self):
|
||||
for task in self:
|
||||
if task.sprint_id and not task.project_id:
|
||||
task.project_id = task.sprint_id.project_id
|
||||
|
||||
@api.constrains("project_id", "sprint_id")
|
||||
def _check_sprint_project(self):
|
||||
for task in self:
|
||||
if task.sprint_id and task.project_id and task.sprint_id.project_id != task.project_id:
|
||||
raise ValidationError(_("Task sprint must belong to the same project as the task."))
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
project_id = vals.get("project_id") or self._context.get("default_project_id")
|
||||
if project_id and "sprint_id" not in vals:
|
||||
project = self.env["project.project"].browse(project_id)
|
||||
if project.require_sprint:
|
||||
sprint = self.env["project.sprint"].search([
|
||||
("project_id", "=", project.id),
|
||||
("status", "=", "in_progress"),
|
||||
], limit=1)
|
||||
if sprint:
|
||||
vals["sprint_id"] = sprint.id
|
||||
return super().create(vals_list)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,112 @@
|
|||
from odoo import models, fields, api, _
|
||||
|
||||
class ProjectSprint(models.Model):
|
||||
_name = "project.sprint"
|
||||
_description = "Project Sprint"
|
||||
_order = "date_start desc"
|
||||
|
||||
project_id = fields.Many2one(
|
||||
"project.project",
|
||||
required=True,
|
||||
ondelete="cascade"
|
||||
)
|
||||
|
||||
sprint_name = fields.Char(string="Sprint Name", required=True)
|
||||
date_start = fields.Date(string="Start Date", required=True)
|
||||
date_end = fields.Date(string="End Date", required=True)
|
||||
|
||||
allocated_hours = fields.Float(string="Allocated Hours")
|
||||
|
||||
status = fields.Selection(
|
||||
[
|
||||
("draft", "Draft"),
|
||||
("in_progress", "In Progress"),
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
|
||||
class ProjectSprint(models.Model):
|
||||
_name = "project.sprint"
|
||||
_description = "Project Sprint"
|
||||
_rec_name = "sprint_name"
|
||||
_order = "date_start desc, id desc"
|
||||
|
||||
project_id = fields.Many2one(
|
||||
"project.project",
|
||||
required=True,
|
||||
ondelete="cascade",
|
||||
index=True,
|
||||
)
|
||||
|
||||
sprint_name = fields.Char(string="Sprint Name", required=True)
|
||||
date_start = fields.Date(string="Start Date", required=True)
|
||||
date_end = fields.Date(string="End Date", required=True)
|
||||
|
||||
allocated_hours = fields.Float(string="Capacity Hours")
|
||||
task_ids = fields.One2many("project.task", "sprint_id", string="Sprint Tasks")
|
||||
task_count = fields.Integer(compute="_compute_sprint_metrics", string="Tasks")
|
||||
completed_task_count = fields.Integer(compute="_compute_sprint_metrics", string="Completed Tasks")
|
||||
planned_hours = fields.Float(compute="_compute_sprint_metrics", string="Planned Hours")
|
||||
actual_hours = fields.Float(compute="_compute_sprint_metrics", string="Actual Hours")
|
||||
remaining_hours = fields.Float(compute="_compute_sprint_metrics", string="Remaining Hours")
|
||||
progress = fields.Float(compute="_compute_sprint_metrics", string="Progress")
|
||||
|
||||
status = fields.Selection(
|
||||
[
|
||||
("draft", "Draft"),
|
||||
("in_progress", "In Progress"),
|
||||
("done", "Completed"),
|
||||
],
|
||||
default="draft",
|
||||
string="Status"
|
||||
)
|
||||
done_date = fields.Date(string="Done Date")
|
||||
note = fields.Text(string="Note")
|
||||
)
|
||||
done_date = fields.Date(string="Done Date")
|
||||
note = fields.Text(string="Note")
|
||||
|
||||
@api.depends(
|
||||
"allocated_hours",
|
||||
"task_ids",
|
||||
"task_ids.state",
|
||||
"task_ids.sprint_estimated_hours",
|
||||
"task_ids.estimated_hours",
|
||||
"task_ids.actual_hours",
|
||||
)
|
||||
def _compute_sprint_metrics(self):
|
||||
done_states = ("1_done", "1_canceled")
|
||||
for sprint in self:
|
||||
tasks = sprint.task_ids
|
||||
sprint.task_count = len(tasks)
|
||||
sprint.completed_task_count = len(tasks.filtered(lambda task: task.state in done_states))
|
||||
sprint.planned_hours = sum(task.sprint_estimated_hours or task.estimated_hours for task in tasks)
|
||||
sprint.actual_hours = sum(tasks.mapped("actual_hours"))
|
||||
sprint.remaining_hours = sprint.allocated_hours - sprint.planned_hours
|
||||
sprint.progress = (sprint.completed_task_count / sprint.task_count * 100.0) if sprint.task_count else 0.0
|
||||
|
||||
@api.constrains("date_start", "date_end")
|
||||
def _check_sprint_dates(self):
|
||||
for sprint in self:
|
||||
if sprint.date_start and sprint.date_end and sprint.date_start > sprint.date_end:
|
||||
raise ValidationError(_("Sprint start date must be before the end date."))
|
||||
|
||||
@api.constrains("project_id", "status")
|
||||
def _check_single_active_sprint(self):
|
||||
for sprint in self.filtered(lambda rec: rec.status == "in_progress"):
|
||||
active_sprint = self.search([
|
||||
("project_id", "=", sprint.project_id.id),
|
||||
("status", "=", "in_progress"),
|
||||
("id", "!=", sprint.id),
|
||||
], limit=1)
|
||||
if active_sprint:
|
||||
raise ValidationError(_(
|
||||
"Only one sprint can be in progress per project. "
|
||||
"Please complete '%s' before starting another sprint."
|
||||
) % active_sprint.display_name)
|
||||
|
||||
def action_start_sprint(self):
|
||||
for sprint in self:
|
||||
if not sprint.task_ids:
|
||||
raise UserError(_("Add at least one task before starting the sprint."))
|
||||
sprint.status = "in_progress"
|
||||
return True
|
||||
|
||||
def action_complete_sprint(self):
|
||||
open_tasks = self.mapped("task_ids").filtered(lambda task: task.state not in ("1_done", "1_canceled"))
|
||||
if open_tasks:
|
||||
raise UserError(_("Complete or cancel all sprint tasks before closing the sprint."))
|
||||
self.write({"status": "done", "done_date": fields.Date.context_today(self)})
|
||||
return True
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
self.write({"status": "draft", "done_date": False})
|
||||
return True
|
||||
|
||||
def action_open_tasks(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"name": _("Sprint Tasks"),
|
||||
"res_model": "project.task",
|
||||
"view_mode": "kanban,list,form,calendar,pivot,graph,activity",
|
||||
"domain": [("sprint_id", "=", self.id)],
|
||||
"context": {
|
||||
"default_project_id": self.project_id.id,
|
||||
"default_sprint_id": self.id,
|
||||
"search_default_project_id": self.project_id.id,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,17 @@ class projectTask(models.Model):
|
|||
_rec_name = 'name'
|
||||
|
||||
user_ids = fields.Many2many()
|
||||
is_project_admin = fields.Boolean(
|
||||
compute="_compute_is_project_admin"
|
||||
)
|
||||
|
||||
@api.depends("project_id","project_id.user_id")
|
||||
def _compute_is_project_admin(self):
|
||||
project_manager = self.project_id.user_id
|
||||
is_admin = self.env.user.has_group("project.group_project_manager") or self.env.user.has_group("base.group_system") or (self.env.user.id == project_manager.id)
|
||||
# Replace the XML ID if your administrator group is different.
|
||||
for rec in self:
|
||||
rec.is_project_admin = is_admin
|
||||
|
||||
involved_user_ids = fields.Many2many(
|
||||
'res.users',
|
||||
|
|
@ -132,6 +143,17 @@ class projectTask(models.Model):
|
|||
store=True,
|
||||
readonly=False
|
||||
)
|
||||
sprint_estimated_hours = fields.Float(
|
||||
string="Sprint Estimated Hours",
|
||||
tracking=True,
|
||||
help="Planning estimate committed during sprint planning. This is kept separate from assignee timeline estimates."
|
||||
)
|
||||
assignee_estimated_hours = fields.Float(
|
||||
string="Assignee Estimated Hours",
|
||||
compute="_compute_assignee_estimated_hours",
|
||||
store=True,
|
||||
help="Estimate calculated from the assignee timelines."
|
||||
)
|
||||
|
||||
def write(self, vals):
|
||||
for rec in self:
|
||||
|
|
@ -560,11 +582,21 @@ class projectTask(models.Model):
|
|||
or current_user in task.project_id.project_lead
|
||||
)
|
||||
)
|
||||
@api.depends('assignees_timelines.estimated_time', 'show_approval_flow')
|
||||
@api.depends('assignees_timelines.estimated_time')
|
||||
def _compute_assignee_estimated_hours(self):
|
||||
for task in self:
|
||||
task.assignee_estimated_hours = sum(task.assignees_timelines.mapped('estimated_time'))
|
||||
|
||||
@api.depends('assignees_timelines.estimated_time', 'show_approval_flow', 'sprint_estimated_hours')
|
||||
def _compute_estimated_hours(self):
|
||||
for task in self:
|
||||
if task.show_approval_flow:
|
||||
task.estimated_hours = sum(task.assignees_timelines.mapped('estimated_time'))
|
||||
assignee_estimate = sum(task.assignees_timelines.mapped('estimated_time'))
|
||||
if task.show_approval_flow and assignee_estimate:
|
||||
task.estimated_hours = assignee_estimate
|
||||
elif task.sprint_estimated_hours:
|
||||
task.estimated_hours = task.sprint_estimated_hours
|
||||
else:
|
||||
task.estimated_hours = task.estimated_hours or 0.0
|
||||
|
||||
@api.depends('timesheet_ids.unit_amount')
|
||||
def _compute_actual_hours(self):
|
||||
|
|
@ -1310,9 +1342,10 @@ class projectTask(models.Model):
|
|||
for task in self:
|
||||
# If user is not allowed to change stage
|
||||
if task.show_approval_flow and not task.can_edit_approval_flow_stages and not self.env.user.has_group("project.group_project_manager"):
|
||||
raise UserError(_(
|
||||
"You are not allowed to change the stage of this task because stage editing is restricted."
|
||||
))
|
||||
if not task.is_project_admin:
|
||||
raise UserError(_(
|
||||
"You are not allowed to change the stage of this task because stage editing is restricted."
|
||||
))
|
||||
|
||||
result = super(projectTask, self).write(vals)
|
||||
if any(field in vals for field in ['allocation_start_date', 'allocation_end_date']):
|
||||
|
|
@ -1942,5 +1975,3 @@ class projectTaskTimelines(models.Model):
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -72,19 +72,30 @@
|
|||
invisible="not assign_approval_flow or not show_back_button or not begin_approval_processing"/>
|
||||
|
||||
</xpath>
|
||||
<xpath expr="//form" position="inside">
|
||||
<field name="begin_approval_processing" invisible="1"/>
|
||||
<field name="showable_stage_ids" invisible="1"/>
|
||||
<field name="assign_approval_flow" invisible="1"/>
|
||||
<field name="manager_level_edit_access" invisible="1"/>
|
||||
<xpath expr="//form" position="inside">
|
||||
<field name="begin_approval_processing" invisible="1"/>
|
||||
<field name="showable_stage_ids" invisible="1"/>
|
||||
<field name="assign_approval_flow" invisible="1"/>
|
||||
<field name="manager_level_edit_access" invisible="1"/>
|
||||
<field name="show_submission_button" invisible="1"/>
|
||||
<field name="show_approval_button" invisible="1"/>
|
||||
<field name="show_refuse_button" invisible="1"/>
|
||||
<field name="show_back_button" invisible="1"/>
|
||||
<field name="is_project_editor" invisible="1"/>
|
||||
<field name="enable_budget_summary" invisible="1"/>
|
||||
<field name="can_edit_stage_in_approval" invisible="1"/>
|
||||
</xpath>
|
||||
<field name="is_project_editor" invisible="1"/>
|
||||
<field name="enable_budget_summary" invisible="1"/>
|
||||
<field name="can_edit_stage_in_approval" invisible="1"/>
|
||||
<field name="active_sprint_id" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<button class="oe_stat_button" type="object" name="action_open_sprint_backlog"
|
||||
icon="fa-list-ul" invisible="not require_sprint">
|
||||
<field name="sprint_backlog_task_count" widget="statinfo" string="Backlog"/>
|
||||
</button>
|
||||
<button class="oe_stat_button" type="object" name="action_open_active_sprint_tasks"
|
||||
icon="fa-play-circle" invisible="not require_sprint or not active_sprint_id">
|
||||
<field name="active_sprint_id" widget="statinfo" string="Active Sprint"/>
|
||||
</button>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='stage_id']" position="attributes">
|
||||
<attribute name="domain">[('id', 'in', showable_stage_ids)]</attribute>
|
||||
</xpath>
|
||||
|
|
@ -506,19 +517,105 @@
|
|||
</field>
|
||||
|
||||
</page>
|
||||
<page name="project_sprints" string="Sprints" invisible="not require_sprint">
|
||||
<field name="sprint_ids">
|
||||
<list editable="bottom">
|
||||
<field name="sprint_name" width="20%"/>
|
||||
<field name="date_start" width="20%"/>
|
||||
<field name="date_end" width="20%"/>
|
||||
<field name="allocated_hours" width="20%"/>
|
||||
<field name="status" width="20%"/>
|
||||
<field name="done_date" optional="hide"/>
|
||||
<field name="note" optional="hide"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page name="project_sprints" string="Sprints" invisible="not require_sprint">
|
||||
<group string="Sprint Planning">
|
||||
<group>
|
||||
<field name="active_sprint_id" readonly="1"/>
|
||||
<field name="sprint_count" readonly="1"/>
|
||||
<field name="sprint_backlog_task_count" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="sprint_planned_hours" widget="timesheet_uom_no_toggle" readonly="1"/>
|
||||
<field name="sprint_actual_hours" widget="timesheet_uom_no_toggle" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<button name="action_open_sprint_backlog" type="object" string="Open Backlog" class="btn-secondary"/>
|
||||
<button name="action_open_active_sprint_tasks" type="object" string="Open Active Sprint" class="btn-primary"
|
||||
invisible="not active_sprint_id"/>
|
||||
</group>
|
||||
<field name="sprint_ids" context="{'default_project_id': id}">
|
||||
<list>
|
||||
<field name="sprint_name"/>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
<field name="allocated_hours" widget="timesheet_uom_no_toggle"/>
|
||||
<field name="planned_hours" widget="timesheet_uom_no_toggle"/>
|
||||
<field name="actual_hours" widget="timesheet_uom_no_toggle"/>
|
||||
<field name="remaining_hours" widget="timesheet_uom_no_toggle"
|
||||
decoration-danger="remaining_hours < 0"/>
|
||||
<field name="task_count"/>
|
||||
<field name="progress" widget="progressbar"/>
|
||||
<field name="status" widget="badge"
|
||||
decoration-muted="status == 'draft'"
|
||||
decoration-info="status == 'in_progress'"
|
||||
decoration-success="status == 'done'"/>
|
||||
<button name="action_start_sprint" type="object" string="Start" class="btn-primary"
|
||||
invisible="status != 'draft'"/>
|
||||
<button name="action_complete_sprint" type="object" string="Complete" class="btn-secondary"
|
||||
invisible="status != 'in_progress'"/>
|
||||
<button name="action_open_tasks" type="object" string="Tasks" class="btn-link"/>
|
||||
<field name="done_date" optional="hide"/>
|
||||
</list>
|
||||
<form string="Sprint">
|
||||
<header>
|
||||
<button name="action_start_sprint" type="object" string="Start Sprint" class="oe_highlight"
|
||||
invisible="status != 'draft'"/>
|
||||
<button name="action_complete_sprint" type="object" string="Complete Sprint" class="oe_highlight"
|
||||
invisible="status != 'in_progress'"/>
|
||||
<button name="action_reset_to_draft" type="object" string="Reset to Draft"
|
||||
invisible="status == 'draft'"/>
|
||||
<field name="status" widget="statusbar" statusbar_visible="draft,in_progress,done"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" type="object" name="action_open_tasks" icon="fa-tasks">
|
||||
<field name="task_count" widget="statinfo" string="Tasks"/>
|
||||
</button>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="project_id" invisible="1"/>
|
||||
<field name="sprint_name"/>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
<field name="allocated_hours" widget="timesheet_uom_no_toggle"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="task_count" readonly="1"/>
|
||||
<field name="completed_task_count" readonly="1"/>
|
||||
<field name="planned_hours" widget="timesheet_uom_no_toggle" readonly="1"/>
|
||||
<field name="actual_hours" widget="timesheet_uom_no_toggle" readonly="1"/>
|
||||
<field name="remaining_hours" widget="timesheet_uom_no_toggle" readonly="1"/>
|
||||
<field name="progress" widget="progressbar" readonly="1"/>
|
||||
<field name="done_date" readonly="1" invisible="not done_date"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Sprint Tasks">
|
||||
<field name="task_ids" context="{'default_project_id': project_id, 'default_sprint_id': id}">
|
||||
<list editable="bottom">
|
||||
<field name="priority" widget="priority" nolabel="1"/>
|
||||
<field name="name"/>
|
||||
<field name="project_id" column_invisible="1"/>
|
||||
<field name="stage_id"/>
|
||||
<field name="user_ids" widget="many2many_avatar_user"/>
|
||||
<field name="sprint_estimated_hours" widget="timesheet_uom_no_toggle"/>
|
||||
<field name="assignee_estimated_hours" widget="timesheet_uom_no_toggle" readonly="1"/>
|
||||
<field name="estimated_hours" widget="timesheet_uom_no_toggle" readonly="1" optional="hide"/>
|
||||
<field name="actual_hours" widget="timesheet_uom_no_toggle" readonly="1"/>
|
||||
<field name="state" widget="project_task_state_selection"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Notes">
|
||||
<field name="note" nolabel="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</page>
|
||||
<page name="development" string="Development Details" invisible="not is_development_user">
|
||||
<group string="Documents">
|
||||
<field name="development_document_ids">
|
||||
|
|
|
|||
|
|
@ -69,6 +69,29 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_view_task_search_form_my_tasks_inherit" model="ir.ui.view">
|
||||
<field name="name">project.task.search.project.by.mytasks</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_search_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//search" position="inside">
|
||||
<field name="model_id" string="Module" filter_domain="[('model_id.name', 'ilike', self)]"/>
|
||||
<separator/>
|
||||
<searchtab name="stage_id_records_tab" string="Stage">
|
||||
<filter string="group_by_module" name="Stage" context="{'group_by': 'stage_id'}"/>
|
||||
</searchtab>
|
||||
<!-- <searchtab name="model_id_records_tab" string="Module">-->
|
||||
<!-- <filter name="group_by_module" string="Module" context="{'group_by': 'model_id'}"/>-->
|
||||
<!-- </searchtab>-->
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project.action_view_my_task" model="ir.actions.act_window">
|
||||
<field name="search_view_id" ref="project_task_timesheet_extended.project_view_task_search_form_my_tasks_inherit"/>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
|
||||
<record id="action_project_by_module" model="ir.actions.act_window">
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
<xpath expr="//field[@name='user_ids']" position="replace">
|
||||
<label for="user_ids" string="Assignees"/>
|
||||
<div class="o_task_assignees_inline">
|
||||
<field name="is_project_admin" invisible="1" force_save="1"/>
|
||||
<field name="user_ids" nolabel="1" widget="involved_assignee_avatar_user" domain="[('id', 'in', involved_user_ids)]" options="{'no_create': True, 'no_quick_create': True, 'no_create_edit': True}" invisible="is_generic"/>
|
||||
<field name="user_ids" nolabel="1" class="o_task_user_field" options="{'no_open': True, 'no_quick_create': True}" widget="many2many_avatar_user" domain="[('id', 'in', assignee_domain_ids)]" invisible="not is_generic"/>
|
||||
<button name="action_open_reassign_wizard" type="object" string="Re-Assign" title="Re-Assign Assignees" class="btn btn-secondary btn-sm o_task_reassign_button" invisible="record_paused or not show_reassign_button"/>
|
||||
|
|
@ -66,7 +67,14 @@
|
|||
<field name="involved_user_ids" widget="many2many_avatar_user" invisible="is_generic"/>
|
||||
<field name="is_generic" readonly="not has_supervisor_access"/>
|
||||
<field name="record_paused" invisible="1"/>
|
||||
<field name="require_sprint" invisible="1"/>
|
||||
<field name="model_id" readonly="not has_supervisor_access" options="{'no_open': True}"/>
|
||||
<field name="sprint_id" invisible="not require_sprint"
|
||||
domain="[('project_id', '=', project_id)]"
|
||||
context="{'default_project_id': project_id}"
|
||||
options="{'no_create_edit': True}"/>
|
||||
<field name="sprint_estimated_hours" invisible="not require_sprint" widget="timesheet_uom_no_toggle"/>
|
||||
<field name="assignee_estimated_hours" widget="timesheet_uom_no_toggle" readonly="1"/>
|
||||
</xpath>
|
||||
<!-- <xpath expr="//field[@name='allocated_hours']" position="after">-->
|
||||
<!-- <field name="estimated_hours"/>-->
|
||||
|
|
@ -168,6 +176,8 @@
|
|||
<!-- </div>-->
|
||||
<field name="estimated_hours" widget="timesheet_uom_no_toggle"
|
||||
readonly="show_approval_flow and timelines_requested"/>
|
||||
<field name="sprint_estimated_hours" widget="timesheet_uom_no_toggle" readonly="1" force_save="1"/>
|
||||
<field name="assignee_estimated_hours" widget="timesheet_uom_no_toggle" readonly="1"/>
|
||||
<field name="actual_hours" widget="timesheet_uom_no_toggle"/>
|
||||
<field name="is_suggested_deadline_warning" />
|
||||
</xpath>
|
||||
|
|
@ -215,6 +225,13 @@
|
|||
<filter name="task_approved" string="Approved" domain="[('approval_status', '=', 'approved')]"/>
|
||||
<filter name="task_rejected" string="Rejected" domain="[('approval_status', '=', 'refused')]"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='project_id']" position="after">
|
||||
<field name="sprint_id" string="Sprint"/>
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='project']" position="after">
|
||||
<filter string="Sprint" name="sprint" context="{'group_by': 'sprint_id'}"/>
|
||||
<filter string="Sprint Backlog" name="sprint_backlog" domain="[('project_id.require_sprint', '=', True), ('sprint_id', '=', False)]"/>
|
||||
</xpath>
|
||||
<!-- <xpath expr="//searchpanel" position="replace"/>-->
|
||||
</field>
|
||||
</record>
|
||||
|
|
@ -228,6 +245,19 @@
|
|||
<xpath expr="//field[@name='subtask_count']" position="after">
|
||||
<field name="task_display_id"/>
|
||||
<field name="module_display_name"/>
|
||||
<field name="sprint_id"/>
|
||||
<field name="sprint_estimated_hours"/>
|
||||
<field name="assignee_estimated_hours"/>
|
||||
<field name="estimated_hours"/>
|
||||
<field name="show_approval_flow"/>
|
||||
<field name="show_submission_button"/>
|
||||
<field name="show_skip_approval_button"/>
|
||||
<field name="show_approval_button"/>
|
||||
<field name="show_refuse_button"/>
|
||||
<field name="show_back_button"/>
|
||||
<field name="timelines_requested"/>
|
||||
<field name="record_paused"/>
|
||||
<field name="is_project_admin"/>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-name='card']//field[@name='name']" position="after">
|
||||
<div class="text-muted mt-1 small">
|
||||
|
|
@ -238,6 +268,46 @@
|
|||
<span>Module: </span>
|
||||
<field name="module_display_name"/>
|
||||
</div>
|
||||
<div t-if="record.sprint_id.raw_value" class="text-muted small">
|
||||
<span>Sprint: </span>
|
||||
<field name="sprint_id"/>
|
||||
</div>
|
||||
<div t-if="record.sprint_estimated_hours.raw_value or record.assignee_estimated_hours.raw_value" class="d-flex flex-wrap gap-2 mt-2 small">
|
||||
<span t-if="record.sprint_estimated_hours.raw_value" class="badge rounded-pill text-bg-light">
|
||||
Sprint <field name="sprint_estimated_hours" widget="timesheet_uom_no_toggle"/>
|
||||
</span>
|
||||
<span t-if="record.assignee_estimated_hours.raw_value" class="badge rounded-pill text-bg-light">
|
||||
Assignee <field name="assignee_estimated_hours" widget="timesheet_uom_no_toggle"/>
|
||||
</span>
|
||||
</div>
|
||||
<div t-if="record.show_approval_flow.raw_value and !record.record_paused.raw_value and !record.is_project_admin.raw_value"
|
||||
class="d-flex flex-wrap gap-1 mt-2">
|
||||
<button t-if="record.show_submission_button.raw_value and !record.show_skip_approval_button.raw_value"
|
||||
type="object" name="submit_for_approval"
|
||||
class="btn btn-primary btn-sm">
|
||||
Request Approval
|
||||
</button>
|
||||
<button t-if="record.show_skip_approval_button.raw_value"
|
||||
type="object" name="submit_for_approval"
|
||||
class="btn btn-primary btn-sm">
|
||||
Next
|
||||
</button>
|
||||
<button t-if="record.show_approval_button.raw_value and record.timelines_requested.raw_value"
|
||||
type="object" name="proceed_further"
|
||||
class="btn btn-success btn-sm">
|
||||
Approve
|
||||
</button>
|
||||
<button t-if="record.show_refuse_button.raw_value and record.timelines_requested.raw_value"
|
||||
type="object" name="action_open_reject_wizard"
|
||||
class="btn btn-outline-danger btn-sm">
|
||||
Reject
|
||||
</button>
|
||||
<button t-if="record.show_back_button.raw_value and record.timelines_requested.raw_value"
|
||||
type="object" name="back_button"
|
||||
class="btn btn-outline-secondary btn-sm">
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
@ -257,6 +327,18 @@
|
|||
<xpath expr="//field[@name='subtask_count']" position="after">
|
||||
<field name="task_display_id"/>
|
||||
<field name="module_display_name"/>
|
||||
<field name="sprint_id"/>
|
||||
<field name="sprint_estimated_hours"/>
|
||||
<field name="assignee_estimated_hours"/>
|
||||
<field name="estimated_hours"/>
|
||||
<field name="show_approval_flow"/>
|
||||
<field name="show_submission_button"/>
|
||||
<field name="show_skip_approval_button"/>
|
||||
<field name="show_approval_button"/>
|
||||
<field name="show_refuse_button"/>
|
||||
<field name="show_back_button"/>
|
||||
<field name="timelines_requested"/>
|
||||
<field name="record_paused"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
@ -271,6 +353,18 @@
|
|||
<xpath expr="//field[@name='subtask_count']" position="after">
|
||||
<field name="task_display_id"/>
|
||||
<field name="module_display_name"/>
|
||||
<field name="sprint_id"/>
|
||||
<field name="sprint_estimated_hours"/>
|
||||
<field name="assignee_estimated_hours"/>
|
||||
<field name="estimated_hours"/>
|
||||
<field name="show_approval_flow"/>
|
||||
<field name="show_submission_button"/>
|
||||
<field name="show_skip_approval_button"/>
|
||||
<field name="show_approval_button"/>
|
||||
<field name="show_refuse_button"/>
|
||||
<field name="show_back_button"/>
|
||||
<field name="timelines_requested"/>
|
||||
<field name="record_paused"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
@ -285,6 +379,32 @@
|
|||
<xpath expr="//field[@name='subtask_count']" position="after">
|
||||
<field name="task_display_id"/>
|
||||
<field name="module_display_name"/>
|
||||
<field name="sprint_id"/>
|
||||
<field name="sprint_estimated_hours"/>
|
||||
<field name="assignee_estimated_hours"/>
|
||||
<field name="estimated_hours"/>
|
||||
<field name="show_approval_flow"/>
|
||||
<field name="show_submission_button"/>
|
||||
<field name="show_skip_approval_button"/>
|
||||
<field name="show_approval_button"/>
|
||||
<field name="show_refuse_button"/>
|
||||
<field name="show_back_button"/>
|
||||
<field name="timelines_requested"/>
|
||||
<field name="record_paused"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_task_list_sprint_inherit" model="ir.ui.view">
|
||||
<field name="name">project.task.list.sprint.inherit</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.project_task_view_tree_main_base"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='milestone_id']" position="after">
|
||||
<field name="sprint_id" optional="show" domain="[('project_id', '=', project_id)]"
|
||||
context="{'default_project_id': project_id}"/>
|
||||
<field name="sprint_estimated_hours" optional="show" widget="timesheet_uom_no_toggle"/>
|
||||
<field name="assignee_estimated_hours" optional="hide" widget="timesheet_uom_no_toggle" readonly="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
|||
Loading…
Reference in New Issue