PMS Updates
This commit is contained in:
parent
4ce02e58fa
commit
0fa84c6d43
|
|
@ -24,6 +24,7 @@ Key Features:
|
||||||
'hr_timesheet',
|
'hr_timesheet',
|
||||||
'base',
|
'base',
|
||||||
'analytic',
|
'analytic',
|
||||||
|
'project_gantt',
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
'security/security.xml',
|
'security/security.xml',
|
||||||
|
|
@ -37,6 +38,10 @@ Key Features:
|
||||||
'view/task_stages.xml',
|
'view/task_stages.xml',
|
||||||
'view/project.xml',
|
'view/project.xml',
|
||||||
'view/project_task.xml',
|
'view/project_task.xml',
|
||||||
|
'view/timesheets.xml',
|
||||||
|
'view/pro_task_gantt.xml',
|
||||||
|
'view/user_availability.xml',
|
||||||
|
# 'view/project_task_gantt.xml',
|
||||||
],
|
],
|
||||||
'assets': {
|
'assets': {
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,20 @@
|
||||||
action = records.action_toggle_pause()
|
action = records.action_toggle_pause()
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- <record id="action_reward_user" model="ir.actions.server">-->
|
||||||
|
<!-- <field name="name">Reward User</field>-->
|
||||||
|
<!-- <field name="model_id" ref="project.model_project_task"/>-->
|
||||||
|
<!-- <field name="binding_model_id" ref="project.model_project_task"/>-->
|
||||||
|
<!-- <field name="binding_type">action</field>-->
|
||||||
|
<!-- <field name="binding_view_types">form</field>-->
|
||||||
|
<!-- <field name="state">code</field>-->
|
||||||
|
<!-- <field name="code">-->
|
||||||
|
<!-- if records:-->
|
||||||
|
<!-- action = records.action_reward_user()-->
|
||||||
|
<!-- </field>-->
|
||||||
|
<!-- </record>-->
|
||||||
|
|
||||||
<data noupdate="1">
|
<data noupdate="1">
|
||||||
<record id="default_projects_channel" model="discuss.channel">
|
<record id="default_projects_channel" model="discuss.channel">
|
||||||
<field name="name">Projects Channel</field>
|
<field name="name">Projects Channel</field>
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,6 @@ from . import teams
|
||||||
from . import task_stages
|
from . import task_stages
|
||||||
from . import project
|
from . import project
|
||||||
from . import project_task
|
from . import project_task
|
||||||
|
from . import timesheets
|
||||||
|
# from . import project_task_gantt
|
||||||
|
from . import user_availability
|
||||||
|
|
@ -15,7 +15,7 @@ class ProjectProject(models.Model):
|
||||||
)
|
)
|
||||||
discuss_channel_id = fields.Many2one(
|
discuss_channel_id = fields.Many2one(
|
||||||
'discuss.channel',
|
'discuss.channel',
|
||||||
string="Project Channel",
|
string="Channel",
|
||||||
domain="[('parent_channel_id', '=', default_projects_channel_id)]",
|
domain="[('parent_channel_id', '=', default_projects_channel_id)]",
|
||||||
help="Select a channel for project communications. Channels must be sub-channels of the main Projects Channel."
|
help="Select a channel for project communications. Channels must be sub-channels of the main Projects Channel."
|
||||||
)
|
)
|
||||||
|
|
@ -159,6 +159,21 @@ class ProjectProject(models.Model):
|
||||||
|
|
||||||
type_ids = fields.Many2many(default=lambda self: self._default_type_ids())
|
type_ids = fields.Many2many(default=lambda self: self._default_type_ids())
|
||||||
|
|
||||||
|
|
||||||
|
estimated_hours = fields.Float(string="Estimated Hours")
|
||||||
|
task_estimated_hours = fields.Float(string="Task Estimated Hours", compute="_compute_task_estimated_hours", store=True)
|
||||||
|
actual_hours = fields.Float(string="Actual Hours", compute="_compute_actual_hours", store=True)
|
||||||
|
|
||||||
|
@api.depends('task_ids.estimated_hours')
|
||||||
|
def _compute_task_estimated_hours(self):
|
||||||
|
for project in self:
|
||||||
|
project.task_estimated_hours = sum(project.task_ids.mapped('estimated_hours'))
|
||||||
|
|
||||||
|
@api.depends('task_ids.timesheet_ids.unit_amount')
|
||||||
|
def _compute_actual_hours(self):
|
||||||
|
for project in self:
|
||||||
|
project.actual_hours = sum(project.task_ids.timesheet_ids.mapped('unit_amount'))
|
||||||
|
|
||||||
def add_users(self):
|
def add_users(self):
|
||||||
return {
|
return {
|
||||||
'type': 'ir.actions.act_window',
|
'type': 'ir.actions.act_window',
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,279 @@
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
from datetime import datetime, date
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
class ProjectTask(models.Model):
|
||||||
|
_inherit = 'project.task'
|
||||||
|
|
||||||
|
# Existing fields
|
||||||
|
gantt_color = fields.Char(compute='_compute_gantt_color', store=True)
|
||||||
|
gantt_user_id = fields.Many2one('res.users', compute='_compute_gantt_user_id', store=True)
|
||||||
|
performance_icon = fields.Char(compute='_compute_performance_icon', store=True)
|
||||||
|
is_overdue = fields.Boolean(compute='_compute_is_overdue', store=True)
|
||||||
|
gantt_date_start = fields.Datetime(compute='_compute_gantt_dates', store=True, inverse='_inverse_gantt_date_start')
|
||||||
|
current_stage_performance = fields.Selection([
|
||||||
|
('good', 'Good'),
|
||||||
|
('normal', 'Normal'),
|
||||||
|
('bad', 'Bad')
|
||||||
|
], compute='_compute_current_stage_performance', store=True)
|
||||||
|
formatted_start_date = fields.Char(compute='_compute_formatted_dates')
|
||||||
|
formatted_end_date = fields.Char(compute='_compute_formatted_dates')
|
||||||
|
|
||||||
|
# New fields for better Gantt view
|
||||||
|
progress = fields.Integer(string="Progress", compute='_compute_progress', store=True)
|
||||||
|
task_priority = fields.Selection([
|
||||||
|
('0', 'Normal'),
|
||||||
|
('1', 'High'),
|
||||||
|
('2', 'Urgent')
|
||||||
|
], default='0', string="Priority")
|
||||||
|
is_completed = fields.Boolean(compute='_compute_is_completed', store=True)
|
||||||
|
completion_date = fields.Datetime(string="Completion Date")
|
||||||
|
days_remaining = fields.Integer(compute='_compute_days_remaining', store=True)
|
||||||
|
|
||||||
|
@api.depends('stage_id', 'project_id.type_ids')
|
||||||
|
def _compute_progress(self):
|
||||||
|
for task in self:
|
||||||
|
if not task.stage_id or not task.project_id.type_ids:
|
||||||
|
task.progress = 0
|
||||||
|
continue
|
||||||
|
|
||||||
|
stages = task.project_id.type_ids.sorted('sequence')
|
||||||
|
total_stages = len(stages)
|
||||||
|
if total_stages == 0:
|
||||||
|
task.progress = 0
|
||||||
|
continue
|
||||||
|
|
||||||
|
current_stage_index = next((i for i, stage in enumerate(stages) if stage.id == task.stage_id.id), 0)
|
||||||
|
task.progress = int((current_stage_index + 1) / total_stages * 100)
|
||||||
|
|
||||||
|
@api.depends('state')
|
||||||
|
def _compute_is_completed(self):
|
||||||
|
for task in self:
|
||||||
|
task.is_completed = task.state in ['1_done', 'done']
|
||||||
|
|
||||||
|
@api.depends('date_deadline')
|
||||||
|
def _compute_days_remaining(self):
|
||||||
|
today = date.today()
|
||||||
|
for task in self:
|
||||||
|
if not task.date_deadline:
|
||||||
|
task.days_remaining = 0
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(task.date_deadline, datetime):
|
||||||
|
deadline_date = task.date_deadline.date()
|
||||||
|
else:
|
||||||
|
deadline_date = task.date_deadline
|
||||||
|
|
||||||
|
delta = deadline_date - today
|
||||||
|
task.days_remaining = delta.days
|
||||||
|
|
||||||
|
@api.depends('planned_date_begin', 'date_deadline')
|
||||||
|
def _compute_formatted_dates(self):
|
||||||
|
for task in self:
|
||||||
|
if task.planned_date_begin:
|
||||||
|
if isinstance(task.planned_date_begin, datetime):
|
||||||
|
task.formatted_start_date = task.planned_date_begin.strftime('%d-%b-%Y %I:%M %p')
|
||||||
|
else:
|
||||||
|
task.formatted_start_date = task.planned_date_begin.strftime('%d-%b-%Y')
|
||||||
|
else:
|
||||||
|
task.formatted_start_date = False
|
||||||
|
|
||||||
|
if task.date_deadline:
|
||||||
|
if isinstance(task.date_deadline, datetime):
|
||||||
|
task.formatted_end_date = task.date_deadline.strftime('%d-%b-%Y %I:%M %p')
|
||||||
|
else:
|
||||||
|
task.formatted_end_date = task.date_deadline.strftime('%d-%b-%Y')
|
||||||
|
else:
|
||||||
|
task.formatted_end_date = False
|
||||||
|
|
||||||
|
@api.depends('show_approval_flow', 'assignees_timelines.actual_time', 'assignees_timelines.estimated_time')
|
||||||
|
def _compute_current_stage_performance(self):
|
||||||
|
for task in self:
|
||||||
|
# Don't compute performance for completed tasks
|
||||||
|
if task.is_completed:
|
||||||
|
task.current_stage_performance = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if task.show_approval_flow:
|
||||||
|
current_timeline = task.assignees_timelines.filtered(lambda t: t.stage_id == task.stage_id)
|
||||||
|
if current_timeline:
|
||||||
|
timeline = current_timeline[0]
|
||||||
|
actual = timeline.actual_time
|
||||||
|
estimated = timeline.estimated_time
|
||||||
|
|
||||||
|
if actual < estimated:
|
||||||
|
task.current_stage_performance = 'good'
|
||||||
|
elif actual == estimated:
|
||||||
|
task.current_stage_performance = 'normal'
|
||||||
|
else:
|
||||||
|
task.current_stage_performance = 'bad'
|
||||||
|
else:
|
||||||
|
task.current_stage_performance = False
|
||||||
|
else:
|
||||||
|
task.current_stage_performance = False
|
||||||
|
|
||||||
|
@api.depends('planned_date_begin')
|
||||||
|
def _compute_gantt_dates(self):
|
||||||
|
for task in self:
|
||||||
|
if task.planned_date_begin:
|
||||||
|
# Ensure we're working with a datetime object
|
||||||
|
if isinstance(task.planned_date_begin, date) and not isinstance(task.planned_date_begin, datetime):
|
||||||
|
# Convert date to datetime at start of day
|
||||||
|
task.gantt_date_start = datetime.combine(task.planned_date_begin, datetime.min.time())
|
||||||
|
else:
|
||||||
|
task.gantt_date_start = task.planned_date_begin
|
||||||
|
else:
|
||||||
|
task.gantt_date_start = False
|
||||||
|
|
||||||
|
def _inverse_gantt_date_start(self):
|
||||||
|
for task in self:
|
||||||
|
if task.gantt_date_start:
|
||||||
|
task.planned_date_begin = task.gantt_date_start
|
||||||
|
|
||||||
|
@api.depends('assignees_timelines.assigned_to', 'user_ids', 'show_approval_flow', 'stage_id')
|
||||||
|
def _compute_gantt_user_id(self):
|
||||||
|
for task in self:
|
||||||
|
if task.show_approval_flow:
|
||||||
|
current_timeline = task.assignees_timelines.filtered(lambda t: t.stage_id == task.stage_id)
|
||||||
|
if current_timeline:
|
||||||
|
task.gantt_user_id = current_timeline[0].assigned_to
|
||||||
|
else:
|
||||||
|
task.gantt_user_id = task.user_ids[:1] if task.user_ids else self.env.user
|
||||||
|
else:
|
||||||
|
task.gantt_user_id = task.user_ids[:1] if task.user_ids else self.env.user
|
||||||
|
|
||||||
|
@api.depends('show_approval_flow', 'assignees_timelines.actual_time', 'assignees_timelines.estimated_time')
|
||||||
|
def _compute_performance_icon(self):
|
||||||
|
for task in self:
|
||||||
|
# Don't show performance icon for completed tasks
|
||||||
|
if task.is_completed:
|
||||||
|
task.performance_icon = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if task.show_approval_flow:
|
||||||
|
current_timeline = task.assignees_timelines.filtered(lambda t: t.stage_id == task.stage_id)
|
||||||
|
if current_timeline:
|
||||||
|
timeline = current_timeline[0]
|
||||||
|
actual = timeline.actual_time
|
||||||
|
estimated = timeline.estimated_time
|
||||||
|
|
||||||
|
if actual < estimated:
|
||||||
|
task.performance_icon = 'fa-smile-o'
|
||||||
|
elif actual == estimated:
|
||||||
|
task.performance_icon = 'fa-meh-o'
|
||||||
|
else:
|
||||||
|
task.performance_icon = 'fa-frown-o'
|
||||||
|
else:
|
||||||
|
task.performance_icon = 'fa-question'
|
||||||
|
else:
|
||||||
|
task.performance_icon = False
|
||||||
|
|
||||||
|
@api.depends('date_deadline')
|
||||||
|
def _compute_is_overdue(self):
|
||||||
|
today = date.today()
|
||||||
|
for task in self:
|
||||||
|
# Completed tasks are never overdue
|
||||||
|
if task.is_completed:
|
||||||
|
task.is_overdue = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not task.date_deadline:
|
||||||
|
task.is_overdue = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Convert datetime to date if needed
|
||||||
|
if isinstance(task.date_deadline, datetime):
|
||||||
|
deadline_date = task.date_deadline.date()
|
||||||
|
else:
|
||||||
|
deadline_date = task.date_deadline
|
||||||
|
|
||||||
|
task.is_overdue = deadline_date < today
|
||||||
|
|
||||||
|
@api.depends('show_approval_flow', 'assignees_timelines.actual_time', 'assignees_timelines.estimated_time',
|
||||||
|
'date_deadline', 'state', 'is_completed')
|
||||||
|
def _compute_gantt_color(self):
|
||||||
|
today = date.today()
|
||||||
|
for task in self:
|
||||||
|
# If task is completed, always show green
|
||||||
|
if task.is_completed:
|
||||||
|
task.gantt_color = '#28a745' # Green
|
||||||
|
continue
|
||||||
|
|
||||||
|
if task.show_approval_flow:
|
||||||
|
current_timeline = task.assignees_timelines.filtered(lambda t: t.stage_id == task.stage_id)
|
||||||
|
if current_timeline:
|
||||||
|
timeline = current_timeline[0]
|
||||||
|
actual = timeline.actual_time
|
||||||
|
estimated = timeline.estimated_time
|
||||||
|
|
||||||
|
if actual < estimated:
|
||||||
|
task.gantt_color = '#28a745' # Green
|
||||||
|
elif actual == estimated:
|
||||||
|
task.gantt_color = '#007bff' # Blue
|
||||||
|
else:
|
||||||
|
task.gantt_color = '#dc3545' # Red
|
||||||
|
else:
|
||||||
|
task.gantt_color = '#6c757d' # Gray if no timeline
|
||||||
|
else:
|
||||||
|
# Color based on deadline
|
||||||
|
if task.date_deadline:
|
||||||
|
# Convert datetime to date if needed
|
||||||
|
if isinstance(task.date_deadline, datetime):
|
||||||
|
deadline_date = task.date_deadline.date()
|
||||||
|
else:
|
||||||
|
deadline_date = task.date_deadline
|
||||||
|
|
||||||
|
if deadline_date < today:
|
||||||
|
task.gantt_color = '#dc3545' # Red for overdue
|
||||||
|
else:
|
||||||
|
task.gantt_color = '#28a745' # Green for on time
|
||||||
|
else:
|
||||||
|
task.gantt_color = '#6c757d' # Gray if no deadline
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _expand_domain_dates(self, domain):
|
||||||
|
new_domain = []
|
||||||
|
for dom in domain:
|
||||||
|
if dom[0] in ['date_start', 'date_end', 'date_deadline', 'gantt_date_start'] and dom[1] in ['>=', '<=', '>',
|
||||||
|
'<', '=']:
|
||||||
|
# Handle both datetime and date string formats
|
||||||
|
if isinstance(dom[2], datetime):
|
||||||
|
min_date = dom[2]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
# Try parsing with time first
|
||||||
|
min_date = datetime.strptime(dom[2], '%Y-%m-%d %H:%M:%S')
|
||||||
|
except ValueError:
|
||||||
|
try:
|
||||||
|
# If that fails, try parsing without time
|
||||||
|
min_date = datetime.strptime(dom[2], '%Y-%m-%d')
|
||||||
|
except ValueError:
|
||||||
|
# If both fail, use the original value
|
||||||
|
min_date = dom[2]
|
||||||
|
new_domain.append((dom[0], dom[1], min_date))
|
||||||
|
else:
|
||||||
|
new_domain.append(dom)
|
||||||
|
return new_domain
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
# Handle date validation more flexibly
|
||||||
|
if 'planned_date_begin' in vals and 'date_deadline' in vals:
|
||||||
|
if vals['planned_date_begin'] and vals['date_deadline']:
|
||||||
|
if vals['planned_date_begin'] > vals['date_deadline']:
|
||||||
|
raise ValidationError(_("Planned start date must be before planned end date."))
|
||||||
|
elif 'planned_date_begin' in vals and vals['planned_date_begin'] and self.date_deadline:
|
||||||
|
if vals['planned_date_begin'] > self.date_deadline:
|
||||||
|
raise ValidationError(_("Planned start date must be before planned end date."))
|
||||||
|
elif 'date_deadline' in vals and vals['date_deadline'] and self.planned_date_begin:
|
||||||
|
if self.planned_date_begin > vals['date_deadline']:
|
||||||
|
raise ValidationError(_("Planned start date must be before planned end date."))
|
||||||
|
|
||||||
|
# Update gantt_date_start when planned_date_begin changes
|
||||||
|
if 'planned_date_begin' in vals:
|
||||||
|
vals['gantt_date_start'] = vals['planned_date_begin']
|
||||||
|
|
||||||
|
# Set completion date when task is marked as done
|
||||||
|
if 'state' in vals and vals['state'] in ['1_done', 'done']:
|
||||||
|
vals['completion_date'] = fields.Datetime.now()
|
||||||
|
|
||||||
|
return super(ProjectTask, self).write(vals)
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
|
||||||
|
class AccountAnalyticLine(models.Model):
|
||||||
|
_inherit = 'account.analytic.line'
|
||||||
|
|
||||||
|
stage_id = fields.Many2one(
|
||||||
|
'project.task.type',
|
||||||
|
string="Stage",
|
||||||
|
domain="[('id', 'in', stage_ids)]"
|
||||||
|
)
|
||||||
|
|
||||||
|
stage_ids = fields.Many2many('project.task.type',related="task_id.project_id.type_ids")
|
||||||
|
|
||||||
|
@api.constrains('stage_id', 'task_id')
|
||||||
|
def _check_stage_required(self):
|
||||||
|
for line in self:
|
||||||
|
if line.task_id.show_approval_flow and not line.stage_id:
|
||||||
|
raise ValidationError(_("Stage is required when Approval Flow is enabled for this task."))
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
|
||||||
|
|
||||||
|
class UserTaskAvailability(models.Model):
|
||||||
|
_name = 'user.task.availability'
|
||||||
|
_description = 'User Task Availability'
|
||||||
|
_auto = False
|
||||||
|
_order = 'work_start_datetime'
|
||||||
|
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
# VIEW FIELDS
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
|
||||||
|
user_id = fields.Many2one('res.users', string="User", readonly=True)
|
||||||
|
task_id = fields.Many2one('project.task', string="Task", readonly=True)
|
||||||
|
project_id = fields.Many2one('project.project', string="Project", readonly=True)
|
||||||
|
stage_id = fields.Many2one('project.task.type', string="Stage", readonly=True)
|
||||||
|
task_name = fields.Char(readonly=True)
|
||||||
|
task_sequence = fields.Char(readonly=True)
|
||||||
|
is_generic = fields.Boolean(readonly=True)
|
||||||
|
|
||||||
|
work_start_datetime = fields.Datetime(string="Work Start", readonly=True)
|
||||||
|
work_end_datetime = fields.Datetime(string="Work End", readonly=True)
|
||||||
|
|
||||||
|
estimated_hours = fields.Float(string="Estimated Hours", readonly=True)
|
||||||
|
actual_hours = fields.Float(string="Actual Hours")
|
||||||
|
|
||||||
|
source_type = fields.Selection([
|
||||||
|
('timeline', 'Timeline'),
|
||||||
|
('task', 'Task'),
|
||||||
|
], string="Source", readonly=True)
|
||||||
|
|
||||||
|
# task state options
|
||||||
|
state = fields.Selection([
|
||||||
|
('01_in_progress', 'In Progress'),
|
||||||
|
('02_changes_requested', 'Changes Requested'),
|
||||||
|
('03_approved', 'Approved'),
|
||||||
|
('1_done', 'Done'),
|
||||||
|
('1_canceled', 'Cancelled'),
|
||||||
|
('04_waiting_normal', 'Waiting'),
|
||||||
|
], string="Task State", readonly=True)
|
||||||
|
|
||||||
|
status_label = fields.Char(string="Status Label", readonly=True)
|
||||||
|
|
||||||
|
progress_emoji = fields.Char(compute='_compute_progress_emoji', string='Progress Emoji')
|
||||||
|
task_status_color = fields.Integer(compute='_compute_task_status_color', string='Status Color')
|
||||||
|
progress_percentage = fields.Float(compute='_compute_progress_percentage', string='Progress %')
|
||||||
|
is_overdue = fields.Boolean(compute='_compute_is_overdue', string='Is Overdue')
|
||||||
|
is_future = fields.Boolean(compute='_compute_is_future', string='Is Future')
|
||||||
|
|
||||||
|
@api.depends('actual_hours', 'estimated_hours')
|
||||||
|
def _compute_progress_emoji(self):
|
||||||
|
for record in self:
|
||||||
|
if record.actual_hours <= record.estimated_hours:
|
||||||
|
record.progress_emoji = '😊'
|
||||||
|
elif record.actual_hours > record.estimated_hours:
|
||||||
|
record.progress_emoji = '😞'
|
||||||
|
else:
|
||||||
|
record.progress_emoji = ''
|
||||||
|
|
||||||
|
@api.depends('state', 'work_start_datetime', 'work_end_datetime')
|
||||||
|
def _compute_task_status_color(self):
|
||||||
|
today = fields.Date.today()
|
||||||
|
for record in self:
|
||||||
|
if record.state == '1_done':
|
||||||
|
record.task_status_color = 10 # Green
|
||||||
|
elif record.state == '1_canceled':
|
||||||
|
record.task_status_color = 0
|
||||||
|
elif record.work_start_datetime and record.work_start_datetime.date() > today:
|
||||||
|
record.task_status_color = 0 # Gray
|
||||||
|
elif record.work_end_datetime and record.work_end_datetime.date() < today and record.state != '1_done':
|
||||||
|
record.task_status_color = 1 # Red
|
||||||
|
else:
|
||||||
|
record.task_status_color = 8 # Default blue
|
||||||
|
|
||||||
|
@api.depends('actual_hours', 'estimated_hours')
|
||||||
|
def _compute_progress_percentage(self):
|
||||||
|
for record in self:
|
||||||
|
if record.estimated_hours > 0:
|
||||||
|
record.progress_percentage = min(100, (record.actual_hours / record.estimated_hours) * 100)
|
||||||
|
else:
|
||||||
|
record.progress_percentage = 0
|
||||||
|
|
||||||
|
@api.depends('work_end_datetime', 'state')
|
||||||
|
def _compute_is_overdue(self):
|
||||||
|
today = fields.Date.today()
|
||||||
|
for record in self:
|
||||||
|
record.is_overdue = (record.work_end_datetime and record.work_end_datetime.date() < today and record.state != '1_done') or (record.actual_hours > record.estimated_hours)
|
||||||
|
|
||||||
|
@api.depends('work_start_datetime')
|
||||||
|
def _compute_is_future(self):
|
||||||
|
today = fields.Date.today()
|
||||||
|
for record in self:
|
||||||
|
record.is_future = record.work_start_datetime and record.work_start_datetime.date() > today
|
||||||
|
@api.depends('task_id', 'task_sequence', 'stage_id')
|
||||||
|
def _compute_display_name(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.display_name = f"{rec.task_sequence}" if rec.task_sequence else rec.task_id.name
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
# CREATE SQL VIEW
|
||||||
|
# --------------------------------------------------------------
|
||||||
|
def init(self):
|
||||||
|
self.env.cr.execute("""DROP VIEW IF EXISTS user_task_availability CASCADE;""")
|
||||||
|
self.env.cr.execute("""
|
||||||
|
CREATE OR REPLACE VIEW user_task_availability AS (
|
||||||
|
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
-- 1️⃣ MAIN SOURCE: project.task.time.lines
|
||||||
|
-- Only when timelines_requested + show_approval_flow are TRUE
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
SELECT
|
||||||
|
ROW_NUMBER() OVER () AS id,
|
||||||
|
tl.assigned_to AS user_id,
|
||||||
|
tl.task_id AS task_id,
|
||||||
|
t.project_id AS project_id,
|
||||||
|
tl.stage_id AS stage_id,
|
||||||
|
t.name AS task_name,
|
||||||
|
t.sequence_name AS task_sequence,
|
||||||
|
t.is_generic AS is_generic,
|
||||||
|
|
||||||
|
tl.estimated_start_datetime AS work_start_datetime,
|
||||||
|
tl.estimated_end_datetime AS work_end_datetime,
|
||||||
|
|
||||||
|
tl.estimated_time AS estimated_hours,
|
||||||
|
tl.actual_time AS actual_hours,
|
||||||
|
|
||||||
|
'timeline' AS source_type,
|
||||||
|
|
||||||
|
t.state AS state,
|
||||||
|
|
||||||
|
CASE
|
||||||
|
WHEN t.state = '01_in_progress' THEN 'In Progress'
|
||||||
|
WHEN t.state = '02_changes_requested' THEN 'Changes Requested'
|
||||||
|
WHEN t.state = '03_approved' THEN 'Approved'
|
||||||
|
WHEN t.state = '1_done' THEN 'Done'
|
||||||
|
WHEN t.state = '1_canceled' THEN 'Cancelled'
|
||||||
|
WHEN t.state = '04_waiting_normal' THEN 'Waiting'
|
||||||
|
ELSE 'Unknown'
|
||||||
|
END AS status_label
|
||||||
|
|
||||||
|
FROM project_task_time_lines tl
|
||||||
|
JOIN project_task t ON t.id = tl.task_id
|
||||||
|
|
||||||
|
WHERE t.timelines_requested = TRUE
|
||||||
|
AND t.show_approval_flow = TRUE
|
||||||
|
AND tl.assigned_to IS NOT NULL
|
||||||
|
AND tl.estimated_start_datetime IS NOT NULL
|
||||||
|
AND tl.estimated_end_datetime IS NOT NULL
|
||||||
|
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
-- 2️⃣ FALLBACK SOURCE: project.task + user_ids (Many2many)
|
||||||
|
-- Only tasks WITHOUT timeline entries for that user
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
SELECT
|
||||||
|
ROW_NUMBER() OVER () + 1000000 AS id, -- avoid overlap
|
||||||
|
rel.user_id AS user_id,
|
||||||
|
t.id AS task_id,
|
||||||
|
t.project_id AS project_id,
|
||||||
|
t.stage_id AS stage_id,
|
||||||
|
t.name AS task_name,
|
||||||
|
t.sequence_name AS task_sequence,
|
||||||
|
t.is_generic AS is_generic,
|
||||||
|
|
||||||
|
COALESCE(t.date_assign, t.create_date) AS work_start_datetime,
|
||||||
|
t.date_deadline AS work_end_datetime,
|
||||||
|
|
||||||
|
t.estimated_hours AS estimated_hours,
|
||||||
|
t.actual_hours AS actual_hours,
|
||||||
|
|
||||||
|
'task' AS source_type,
|
||||||
|
|
||||||
|
t.state AS state,
|
||||||
|
|
||||||
|
CASE
|
||||||
|
WHEN t.state = '01_in_progress' THEN 'In Progress'
|
||||||
|
WHEN t.state = '02_changes_requested' THEN 'Changes Requested'
|
||||||
|
WHEN t.state = '03_approved' THEN 'Approved'
|
||||||
|
WHEN t.state = '1_done' THEN 'Done'
|
||||||
|
WHEN t.state = '1_canceled' THEN 'Cancelled'
|
||||||
|
WHEN t.state = '04_waiting_normal' THEN 'Waiting'
|
||||||
|
ELSE 'Unknown'
|
||||||
|
END AS status_label
|
||||||
|
|
||||||
|
FROM project_task t
|
||||||
|
JOIN project_task_user_rel rel ON rel.task_id = t.id
|
||||||
|
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM project_task_time_lines tl2
|
||||||
|
WHERE tl2.task_id = t.id
|
||||||
|
AND tl2.assigned_to = rel.user_id
|
||||||
|
AND tl2.estimated_start_datetime IS NOT NULL
|
||||||
|
AND tl2.estimated_end_datetime IS NOT NULL
|
||||||
|
)
|
||||||
|
|
||||||
|
);
|
||||||
|
""")
|
||||||
|
|
@ -19,3 +19,5 @@ access_project_tags_supervisor,project.project_tags_supervisor,project.model_pro
|
||||||
|
|
||||||
access_project_task_time_lines_user,access_project_task_time_lines_user,model_project_task_time_lines,base.group_user,1,1,1,1
|
access_project_task_time_lines_user,access_project_task_time_lines_user,model_project_task_time_lines,base.group_user,1,1,1,1
|
||||||
access_project_task_time_lines_manager,access_project_task_time_lines_manager,model_project_task_time_lines,project.group_project_manager,1,1,1,1
|
access_project_task_time_lines_manager,access_project_task_time_lines_manager,model_project_task_time_lines,project.group_project_manager,1,1,1,1
|
||||||
|
|
||||||
|
access_user_task_availability,user.task.availability.access,model_user_task_availability,base.group_user,1,0,0,0
|
||||||
|
|
|
@ -65,6 +65,19 @@
|
||||||
<field name="perm_unlink" eval="0"/>
|
<field name="perm_unlink" eval="0"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.rule" id="user_task_availability_project_lead_rule">
|
||||||
|
<field name="name">Task Availability: project lead: see all user tasks</field>
|
||||||
|
<field name="model_id" ref="model_user_task_availability"/>
|
||||||
|
<field name="groups" eval="[(4,ref('base.group_user')),(4,ref('project.group_project_user'))]"/>
|
||||||
|
<field name="domain_force">[
|
||||||
|
'|', '|',
|
||||||
|
('project_id.project_lead', '=', user.id),
|
||||||
|
('user_id', '=', user.id),
|
||||||
|
('project_id.user_id', '=', user.id),
|
||||||
|
]
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<!-- <record model="ir.rule" id="timesheet_users_normal_timesheets">-->
|
<!-- <record model="ir.rule" id="timesheet_users_normal_timesheets">-->
|
||||||
<!-- <field name="name">timesheet: users: see own tasks</field>-->
|
<!-- <field name="name">timesheet: users: see own tasks</field>-->
|
||||||
<!-- <field name="model_id" ref="analytic.model_account_analytic_line"/>-->
|
<!-- <field name="model_id" ref="analytic.model_account_analytic_line"/>-->
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="project_gantt.project_task_view_gantt" model="ir.ui.view">
|
||||||
|
<field name="name">project.task.view.gantt</field>
|
||||||
|
<field name="model">project.task</field>
|
||||||
|
<field name="priority">10</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<gantt date_start="planned_date_begin"
|
||||||
|
form_view_id="%(project_gantt.project_task_view_form_in_gantt)d"
|
||||||
|
date_stop="date_deadline"
|
||||||
|
default_scale="month"
|
||||||
|
scales="day,week,month,year"
|
||||||
|
color="project_id"
|
||||||
|
string="Planning"
|
||||||
|
js_class="task_gantt"
|
||||||
|
display_unavailability="1"
|
||||||
|
precision="{'day': 'hour:quarter', 'week': 'day:half', 'month': 'day:half'}"
|
||||||
|
decoration-danger="planning_overlap"
|
||||||
|
default_group_by="user_ids"
|
||||||
|
progress_bar="user_ids"
|
||||||
|
pill_label="True"
|
||||||
|
total_row="True"
|
||||||
|
dependency_field="depend_on_ids"
|
||||||
|
dependency_inverted_field="dependent_ids">
|
||||||
|
<templates>
|
||||||
|
<div t-name="gantt-popover">
|
||||||
|
<div name="project_id">
|
||||||
|
<strong>Project — </strong>
|
||||||
|
<t t-if="project_id" t-esc="project_id[1]"/>
|
||||||
|
<t t-else=""><span class="fst-italic text-muted"><i class="fa fa-lock"></i> Private</span></t>
|
||||||
|
</div>
|
||||||
|
<div t-if="allow_milestones and milestone_id" groups="project.group_project_milestone">
|
||||||
|
<strong>Milestone — </strong> <t t-esc="milestone_id[1]"/>
|
||||||
|
</div>
|
||||||
|
<div t-if="user_names"><strong>Assignees — </strong> <t t-esc="user_names"/></div>
|
||||||
|
<div t-if="partner_id"><strong>Customer — </strong> <t t-esc="partner_id[1]"/></div>
|
||||||
|
<div t-if="project_id" name="allocated_hours"><strong>Allocated Time — </strong> <t t-esc="allocated_hours"/></div>
|
||||||
|
<div t-if="project_id">
|
||||||
|
<t t-esc="planned_date_begin.toFormat('f ')"/>
|
||||||
|
<i class="fa fa-long-arrow-right" title="Arrow"/>
|
||||||
|
<t t-esc="date_deadline.toFormat(' f')"/>
|
||||||
|
</div>
|
||||||
|
<div class="text-danger mt-2" t-if="planning_overlap">
|
||||||
|
<t t-out="planningOverlapHtml"/>
|
||||||
|
</div>
|
||||||
|
<footer replace="0">
|
||||||
|
<button name="action_unschedule_task" type="object" string="Unschedule" class="btn btn-sm btn-secondary"/>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</templates>
|
||||||
|
<field name="project_id"/>
|
||||||
|
<field name="allow_milestones"/>
|
||||||
|
<field name="milestone_id"/>
|
||||||
|
<field name="user_ids"/>
|
||||||
|
<field name="user_names"/>
|
||||||
|
<field name="partner_id"/>
|
||||||
|
<field name="planning_overlap"/>
|
||||||
|
<field name="allocated_hours"/>
|
||||||
|
</gantt>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
</h1>
|
</h1>
|
||||||
</group>
|
</group>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
<xpath expr="//field[@name='user_id']" position="after">
|
<xpath expr="//field[@name='user_id']" position="after">
|
||||||
<field name="project_lead" widget="many2one_avatar_user"/>
|
<field name="project_lead" widget="many2one_avatar_user"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
@ -80,4 +81,17 @@
|
||||||
</page>
|
</page>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="project_invoice_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">project.invoice.inherit.form.view</field>
|
||||||
|
<field name="model">project.project</field>
|
||||||
|
<field name="inherit_id" ref="hr_timesheet.project_invoice_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='allocated_hours']" position="after">
|
||||||
|
<field name="estimated_hours" widget="timesheet_uom_no_toggle" invisible="not allow_timesheets" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||||
|
<field name="task_estimated_hours" widget="timesheet_uom_no_toggle" invisible="not allow_timesheets" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||||
|
<field name="actual_hours" widget="timesheet_uom_no_toggle" invisible="not allow_timesheets" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
@ -23,23 +23,33 @@
|
||||||
<!-- <field name="assigned_team"/>-->
|
<!-- <field name="assigned_team"/>-->
|
||||||
<!-- </xpath>-->
|
<!-- </xpath>-->
|
||||||
<xpath expr="//field[@name='user_ids']" position="after">
|
<xpath expr="//field[@name='user_ids']" position="after">
|
||||||
<field name="is_generic"/>
|
<field name="is_generic" readonly="not has_supervisor_access"/>
|
||||||
<field name="record_paused" invisible="1"/>
|
<field name="record_paused" invisible="1"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
<!-- <xpath expr="//field[@name='allocated_hours']" position="after">-->
|
||||||
|
<!-- <field name="estimated_hours"/>-->
|
||||||
|
<!-- <field name="actual_hours"/>-->
|
||||||
|
<!-- </xpath>-->
|
||||||
|
|
||||||
<xpath expr="//sheet/notebook" position="inside">
|
<xpath expr="//sheet/notebook" position="inside">
|
||||||
<page string="Assignees Timelines" invisible="not show_approval_flow">
|
<page string="Assignees Timelines" invisible="not show_approval_flow">
|
||||||
<button name="button_update_assignees" type="object" string="Update Assignees" class="oe_highlight"/>
|
<button name="button_update_assignees" type="object" string="Update Assignees" class="oe_highlight"/>
|
||||||
|
<button name="action_assign_approx_deadlines" type="object" string="Assign Approx Timeline" class="oe_highlight"/>
|
||||||
<field name="assignees_timelines" context="{'default_task_id': id}" >
|
<field name="assignees_timelines" context="{'default_task_id': id}" >
|
||||||
<list editable="bottom">
|
<list editable="bottom">
|
||||||
<field name="stage_id"/>
|
<field name="stage_id" readonly="not has_edit_access"/>
|
||||||
<field name="responsible_lead"/>
|
<field name="responsible_lead" readonly="not has_edit_access"/>
|
||||||
<field name="team_id"/>
|
<field name="team_id" readonly="not has_edit_access"/>
|
||||||
<field name="assigned_to"/>
|
<field name="assigned_to" readonly="not has_edit_access"/>
|
||||||
<!-- <field name="team_all_member_ids" widget="many2many_tags"/>-->
|
<!-- <field name="team_all_member_ids" widget="many2many_tags"/>-->
|
||||||
<field name="estimated_time" widget="float_time"/>
|
<field name="estimated_time" widget="float_time" readonly="not has_edit_access or estimated_time_readonly"/>
|
||||||
<field name="actual_time" readonly="1" optional="hide" widget="float_time"/>
|
<field name="actual_time" readonly="1" optional="hide" widget="float_time"/>
|
||||||
<field name="request_date" readonly="1" optional="hide"/>
|
<field name="has_edit_access" column_invisible="True" optional="hide" readonly="1"/>
|
||||||
<field name="done_date" readonly="1" optional="hide"/>
|
<field name="estimated_time_readonly" column_invisible="True" optional="hide" readonly="1"/>
|
||||||
|
<field name="estimated_start_datetime" optional="show" readonly="not has_edit_access"/>
|
||||||
|
<field name="estimated_end_datetime" optional="show" readonly="not has_edit_access"/>
|
||||||
|
<!-- <field name="request_date" readonly="1" optional="hide"/>-->
|
||||||
|
<!-- <field name="done_date" readonly="1" optional="hide"/>-->
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
|
|
@ -48,11 +58,11 @@
|
||||||
</page>
|
</page>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//header" position="inside">
|
<xpath expr="//header" position="inside">
|
||||||
<button type="object" name="request_timelines" string="Request Timelines" class="oe_highlight" invisible="not show_approval_flow or timelines_requested"/>
|
<button type="object" name="request_timelines" string="Request Timelines" class="oe_highlight" invisible="not show_approval_flow or timelines_requested or record_paused"/>
|
||||||
<button type="object" name="submit_for_approval" string="Request Approval" class="oe_highlight" invisible="not show_approval_flow or not show_submission_button or not timelines_requested"/>
|
<button type="object" name="submit_for_approval" string="Request Approval" class="oe_highlight" invisible="not show_approval_flow or not show_submission_button or not timelines_requested or record_paused"/>
|
||||||
<button type="object" name="proceed_further" string="Approve & Proceed" class="oe_highlight" invisible="not show_approval_flow or not show_approval_button or not timelines_requested"/>
|
<button type="object" name="proceed_further" string="Approve & Proceed" class="oe_highlight" invisible="not show_approval_flow or not show_approval_button or not timelines_requested or record_paused"/>
|
||||||
<button type="object" name="action_open_reject_wizard" string="Reject & Return" class="oe_highlight" invisible="not show_approval_flow or not show_refuse_button or not timelines_requested"/>
|
<button type="object" name="action_open_reject_wizard" string="Reject & Return" class="oe_highlight" invisible="not show_approval_flow or not show_refuse_button or not timelines_requested or record_paused"/>
|
||||||
<button type="object" name="back_button" string="Go Back" class="oe_highlight" invisible="not show_approval_flow or not show_back_button or not timelines_requested"/>
|
<button type="object" name="back_button" string="Go Back" class="oe_highlight" invisible="not show_approval_flow or not show_back_button or not timelines_requested or record_paused"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//form" position="inside">
|
<xpath expr="//form" position="inside">
|
||||||
<field name="approval_status" invisible="1"/>
|
<field name="approval_status" invisible="1"/>
|
||||||
|
|
@ -62,6 +72,7 @@
|
||||||
<field name="show_approval_button" invisible="1"/>
|
<field name="show_approval_button" invisible="1"/>
|
||||||
<field name="show_refuse_button" invisible="1"/>
|
<field name="show_refuse_button" invisible="1"/>
|
||||||
<field name="show_back_button" invisible="1"/>
|
<field name="show_back_button" invisible="1"/>
|
||||||
|
<field name="has_supervisor_access" invisible="1"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='stage_id']" position="attributes">
|
<xpath expr="//field[@name='stage_id']" position="attributes">
|
||||||
<attribute name="readonly">show_approval_flow or state in ['1_canceled','04_waiting_normal'] or record_paused</attribute>
|
<attribute name="readonly">show_approval_flow or state in ['1_canceled','04_waiting_normal'] or record_paused</attribute>
|
||||||
|
|
@ -76,6 +87,47 @@
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="view_task_form2_inherited_timesheet" model="ir.ui.view">
|
||||||
|
<field name="name">project.task.form.inherit</field>
|
||||||
|
<field name="model">project.task</field>
|
||||||
|
<field name="inherit_id" ref="hr_timesheet.view_task_form2_inherited"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//label[@for='allocated_hours']" position="before">
|
||||||
|
<div role="alert" class="alert alert-warning d-flex flex-wrap gap-3"
|
||||||
|
invisible="not is_suggested_deadline_warning">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
|
||||||
|
<i class="fa fa-exclamation-triangle text-danger me-2" role="img" title="Deadline Issue"/>
|
||||||
|
Based on the timelines, the deadline can't be met
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
Suggested Deadline:
|
||||||
|
<field name="suggested_deadline" widget="date"/>
|
||||||
|
<field name="is_suggested_deadline_warning" invisible="1"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div>-->
|
||||||
|
<!-- <field name="suggested_deadline" invisible="not suggested_deadline or not show_approval_flow or not timelines_requested"/>-->
|
||||||
|
<!-- <field name="is_suggested_deadline_warning" invisible="1"/>-->
|
||||||
|
<!-- <label for="suggested_deadline"-->
|
||||||
|
<!-- class="text-warning"-->
|
||||||
|
<!-- invisible="not is_suggested_deadline_warning">-->
|
||||||
|
<!-- ⚠ Based on the timelines, the deadline can't be met-->
|
||||||
|
<!-- </label>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<field name="estimated_hours" widget="timesheet_uom_no_toggle" readonly="show_approval_flow and timelines_requested"/>
|
||||||
|
<field name="actual_hours" widget="timesheet_uom_no_toggle"/>
|
||||||
|
<field name="is_suggested_deadline_warning" />
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//page[@name='page_timesheets']/field[@name='timesheet_ids']/list/field[@name='name']" position="after">
|
||||||
|
<field name="stage_id" required="0" readonly="readonly_timesheet" options="{'no_create': True,'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record id="view_task_kanban_inherit" model="ir.ui.view">
|
<record id="view_task_kanban_inherit" model="ir.ui.view">
|
||||||
<field name="name">project.task.kanban.inherit</field>
|
<field name="name">project.task.kanban.inherit</field>
|
||||||
<field name="model">project.task</field>
|
<field name="model">project.task</field>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
<!-- <record id="project_gantt.project_task_view_gantt" model="ir.ui.view">-->
|
||||||
|
<!-- <field name="name">project.task.view.gantt.custom</field>-->
|
||||||
|
<!-- <field name="model">project.task</field>-->
|
||||||
|
<!-- <field name="priority">20</field>-->
|
||||||
|
<!-- <field name="arch" type="xml">-->
|
||||||
|
<!-- <gantt date_start="gantt_date_start"-->
|
||||||
|
<!-- date_stop="date_deadline"-->
|
||||||
|
<!-- default_scale="month"-->
|
||||||
|
<!-- scales="day,week,month,year"-->
|
||||||
|
<!-- color="gantt_color"-->
|
||||||
|
<!-- string="Task Planning"-->
|
||||||
|
<!-- js_class="task_gantt"-->
|
||||||
|
<!-- display_unavailability="1"-->
|
||||||
|
<!-- precision="{'day': 'hour:quarter', 'week': 'day:half', 'month': 'day:half'}"-->
|
||||||
|
<!-- decoration-danger="is_overdue"-->
|
||||||
|
<!-- decoration-info="priority == '1'"-->
|
||||||
|
<!-- default_group_by="gantt_user_id"-->
|
||||||
|
<!-- progress_bar="user_ids"-->
|
||||||
|
<!-- pill_label="True"-->
|
||||||
|
<!-- total_row="True"-->
|
||||||
|
<!-- dependency_field="depend_on_ids"-->
|
||||||
|
<!-- dependency_inverted_field="dependent_ids">-->
|
||||||
|
<!-- <templates>-->
|
||||||
|
<!-- <div t-name="gantt-popover">-->
|
||||||
|
<!-- <div class="o_gantt_popover_header">-->
|
||||||
|
<!-- <strong t-esc="name"/>-->
|
||||||
|
<!-- <span t-if="priority == '1'" class="float-end text-warning">-->
|
||||||
|
<!-- <i class="fa fa-star"/>-->
|
||||||
|
<!-- High Priority-->
|
||||||
|
<!-- </span>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div name="project_id">-->
|
||||||
|
<!-- <strong>Project —</strong>-->
|
||||||
|
<!-- <t t-if="project_id" t-esc="project_id[1]"/>-->
|
||||||
|
<!-- <t t-else="">-->
|
||||||
|
<!-- <span class="fst-italic text-muted">-->
|
||||||
|
<!-- <i class="fa fa-lock"></i>-->
|
||||||
|
<!-- Private-->
|
||||||
|
<!-- </span>-->
|
||||||
|
<!-- </t>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div t-if="show_approval_flow and current_stage_performance">-->
|
||||||
|
<!-- <strong>Performance —</strong>-->
|
||||||
|
<!-- <t t-if="current_stage_performance == 'good'">-->
|
||||||
|
<!-- <span class="text-success">-->
|
||||||
|
<!-- <i class="fa fa-smile-o"></i>-->
|
||||||
|
<!-- Good (Actual < Estimated)-->
|
||||||
|
<!-- </span>-->
|
||||||
|
<!-- </t>-->
|
||||||
|
<!-- <t t-elif="current_stage_performance == 'normal'">-->
|
||||||
|
<!-- <span class="text-primary">-->
|
||||||
|
<!-- <i class="fa fa-meh-o"></i>-->
|
||||||
|
<!-- Normal (Actual == Estimated)-->
|
||||||
|
<!-- </span>-->
|
||||||
|
<!-- </t>-->
|
||||||
|
<!-- <t t-elif="current_stage_performance == 'bad'">-->
|
||||||
|
<!-- <span class="text-danger">-->
|
||||||
|
<!-- <i class="fa fa-frown-o"></i>-->
|
||||||
|
<!-- Bad (Actual > Estimated)-->
|
||||||
|
<!-- </span>-->
|
||||||
|
<!-- </t>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div t-if="allow_milestones and milestone_id" groups="project.group_project_milestone">-->
|
||||||
|
<!-- <strong>Milestone —</strong>-->
|
||||||
|
<!-- <t t-esc="milestone_id[1]"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div t-if="user_names">-->
|
||||||
|
<!-- <strong>Assignees —</strong>-->
|
||||||
|
<!-- <t t-esc="user_names"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div t-if="partner_id">-->
|
||||||
|
<!-- <strong>Customer —</strong>-->
|
||||||
|
<!-- <t t-esc="partner_id[1]"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div t-if="show_approval_flow">-->
|
||||||
|
<!-- <strong>Timeline —</strong>-->
|
||||||
|
<!-- <t t-esc="estimated_hours"/>-->
|
||||||
|
<!-- hours estimated /-->
|
||||||
|
<!-- <t t-esc="actual_hours"/>-->
|
||||||
|
<!-- hours actual-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div t-if="project_id" name="allocated_hours">-->
|
||||||
|
<!-- <strong>Allocated Time —</strong>-->
|
||||||
|
<!-- <t t-esc="allocated_hours"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div t-if="formatted_start_date and formatted_end_date">-->
|
||||||
|
<!-- <strong>Dates: </strong>-->
|
||||||
|
<!-- <t t-esc="formatted_start_date"/>-->
|
||||||
|
<!-- <i class="fa fa-long-arrow-right" title="Arrow"/>-->
|
||||||
|
<!-- <t t-esc="formatted_end_date"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div t-elif="formatted_start_date">-->
|
||||||
|
<!-- <strong>Start Date: </strong>-->
|
||||||
|
<!-- <t t-esc="formatted_start_date"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div t-elif="formatted_end_date">-->
|
||||||
|
<!-- <strong>Deadline: </strong>-->
|
||||||
|
<!-- <t t-esc="formatted_end_date"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div class="text-danger mt-2" t-if="planning_overlap">-->
|
||||||
|
<!-- <t t-out="planningOverlapHtml"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <footer replace="0">-->
|
||||||
|
<!-- <button name="action_unschedule_task" type="object" string="Unschedule"-->
|
||||||
|
<!-- class="btn btn-sm btn-secondary"/>-->
|
||||||
|
<!-- </footer>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div t-name="gantt-pulse">-->
|
||||||
|
<!-- <div class="o_gantt_pill">-->
|
||||||
|
<!-- <t t-if="priority == '1'">-->
|
||||||
|
<!-- <i class="fa fa-star text-warning me-1"/>-->
|
||||||
|
<!-- </t>-->
|
||||||
|
<!-- <t t-if="show_approval_flow and performance_icon">-->
|
||||||
|
<!-- <i t-attf-class="fa {{performance_icon}} me-1"/>-->
|
||||||
|
<!-- </t>-->
|
||||||
|
<!-- <t t-esc="name"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </templates>-->
|
||||||
|
|
||||||
|
<!-- <field name="project_id"/>-->
|
||||||
|
<!-- <field name="allow_milestones"/>-->
|
||||||
|
<!-- <field name="milestone_id"/>-->
|
||||||
|
<!-- <field name="user_ids"/>-->
|
||||||
|
<!-- <field name="user_names"/>-->
|
||||||
|
<!-- <field name="partner_id"/>-->
|
||||||
|
<!-- <field name="planning_overlap"/>-->
|
||||||
|
<!-- <field name="allocated_hours"/>-->
|
||||||
|
<!-- <field name="gantt_color"/>-->
|
||||||
|
<!-- <field name="gantt_user_id"/>-->
|
||||||
|
<!-- <field name="performance_icon"/>-->
|
||||||
|
<!-- <field name="show_approval_flow"/>-->
|
||||||
|
<!-- <field name="priority"/>-->
|
||||||
|
<!-- <field name="current_stage_performance"/>-->
|
||||||
|
<!-- <field name="estimated_hours"/>-->
|
||||||
|
<!-- <field name="actual_hours"/>-->
|
||||||
|
<!-- <field name="is_overdue"/>-->
|
||||||
|
<!-- <field name="gantt_date_start"/>-->
|
||||||
|
<!-- <field name="planned_date_begin"/>-->
|
||||||
|
<!-- <field name="date_deadline"/>-->
|
||||||
|
<!-- <field name="formatted_start_date"/>-->
|
||||||
|
<!-- <field name="formatted_end_date"/>-->
|
||||||
|
<!-- </gantt>-->
|
||||||
|
<!-- </field>-->
|
||||||
|
<!-- </record>-->
|
||||||
|
<record id="project_gantt.project_task_view_gantt" model="ir.ui.view">
|
||||||
|
<field name="name">project.task.view.gantt.enhanced</field>
|
||||||
|
<field name="model">project.task</field>
|
||||||
|
<field name="priority">30</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<gantt date_start="gantt_date_start"
|
||||||
|
date_stop="date_deadline"
|
||||||
|
default_scale="month"
|
||||||
|
scales="day,week,month,year"
|
||||||
|
color="gantt_color"
|
||||||
|
string="Task Planning"
|
||||||
|
js_class="task_gantt"
|
||||||
|
display_unavailability="1"
|
||||||
|
precision="{'day': 'hour:quarter', 'week': 'day:half', 'month': 'day:half'}"
|
||||||
|
decoration-danger="is_overdue"
|
||||||
|
decoration-info="task_priority == '1'"
|
||||||
|
decoration-warning="task_priority == '2'"
|
||||||
|
decoration-success="is_completed"
|
||||||
|
default_group_by="gantt_user_id"
|
||||||
|
progress_bar="progress"
|
||||||
|
pill_label="True"
|
||||||
|
total_row="True"
|
||||||
|
dependency_field="depend_on_ids"
|
||||||
|
dependency_inverted_field="dependent_ids">
|
||||||
|
<templates>
|
||||||
|
<div t-name="gantt-popover">
|
||||||
|
<div class="o_gantt_popover_header d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<strong t-esc="name"/>
|
||||||
|
<span t-if="task_priority == '1'" class="badge bg-warning ms-2">
|
||||||
|
<i class="fa fa-exclamation"/> High Priority
|
||||||
|
</span>
|
||||||
|
<span t-if="task_priority == '2'" class="badge bg-danger ms-2">
|
||||||
|
<i class="fa fa-fire"/> Urgent
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span t-if="is_completed" class="badge bg-success">
|
||||||
|
<i class="fa fa-check-circle"/> Completed
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<div name="project_id">
|
||||||
|
<strong>Project —</strong>
|
||||||
|
<t t-if="project_id" t-esc="project_id[1]"/>
|
||||||
|
<t t-else="">
|
||||||
|
<span class="fst-italic text-muted">
|
||||||
|
<i class="fa fa-lock"></i> Private
|
||||||
|
</span>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-if="show_approval_flow and current_stage_performance and not is_completed" class="mt-1">
|
||||||
|
<strong>Performance —</strong>
|
||||||
|
<t t-if="current_stage_performance == 'good'">
|
||||||
|
<span class="text-success">
|
||||||
|
<i class="fa fa-smile-o"></i> Good (Actual < Estimated)
|
||||||
|
</span>
|
||||||
|
</t>
|
||||||
|
<t t-elif="current_stage_performance == 'normal'">
|
||||||
|
<span class="text-primary">
|
||||||
|
<i class="fa fa-meh-o"></i> Normal (Actual == Estimated)
|
||||||
|
</span>
|
||||||
|
</t>
|
||||||
|
<t t-elif="current_stage_performance == 'bad'">
|
||||||
|
<span class="text-danger">
|
||||||
|
<i class="fa fa-frown-o"></i> Bad (Actual > Estimated)
|
||||||
|
</span>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-if="allow_milestones and milestone_id" groups="project.group_project_milestone" class="mt-1">
|
||||||
|
<strong>Milestone —</strong> <t t-esc="milestone_id[1]"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-if="user_names" class="mt-1">
|
||||||
|
<strong>Assignees —</strong> <t t-esc="user_names"/>
|
||||||
|
</div>
|
||||||
|
<div t-if="partner_id" class="mt-1">
|
||||||
|
<strong>Customer —</strong> <t t-esc="partner_id[1]"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-if="show_approval_flow and not is_completed" class="mt-1">
|
||||||
|
<strong>Timeline —</strong>
|
||||||
|
<t t-esc="estimated_hours"/> hours estimated /
|
||||||
|
<t t-esc="actual_hours"/> hours actual
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-if="project_id" name="allocated_hours" class="mt-1">
|
||||||
|
<strong>Allocated Time —</strong> <t t-esc="allocated_hours"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<strong>Progress:</strong>
|
||||||
|
<span><t t-esc="progress"/>%</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress mt-1">
|
||||||
|
<div class="progress-bar" role="progressbar"
|
||||||
|
t-attf-style="width: {{progress}}%"
|
||||||
|
t-att-aria-valuenow="progress"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-if="formatted_start_date and formatted_end_date" class="mt-2">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div>
|
||||||
|
<strong>Start:</strong> <t t-esc="formatted_start_date"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>End:</strong> <t t-esc="formatted_end_date"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div t-elif="formatted_start_date" class="mt-1">
|
||||||
|
<strong>Start Date: </strong> <t t-esc="formatted_start_date"/>
|
||||||
|
</div>
|
||||||
|
<div t-elif="formatted_end_date" class="mt-1">
|
||||||
|
<strong>Deadline: </strong> <t t-esc="formatted_end_date"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-if="days_remaining != 0 and not is_completed" class="mt-1">
|
||||||
|
<t t-if="days_remaining > 0">
|
||||||
|
<span class="text-success">
|
||||||
|
<i class="fa fa-clock-o"></i> <t t-esc="days_remaining"/> days remaining
|
||||||
|
</span>
|
||||||
|
</t>
|
||||||
|
<t t-else="">
|
||||||
|
<span class="text-danger">
|
||||||
|
<i class="fa fa-exclamation-triangle"></i> <t t-esc="abs(days_remaining)"/> days overdue
|
||||||
|
</span>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-danger mt-2" t-if="planning_overlap">
|
||||||
|
<t t-out="planningOverlapHtml"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="mt-3">
|
||||||
|
<button name="action_unschedule_task" type="object" string="Unschedule" class="btn btn-sm btn-secondary"/>
|
||||||
|
<button name="button_update_assignees" type="object" string="Update Assignees" class="btn btn-sm btn-primary"/>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="gantt-pulse">
|
||||||
|
<div class="o_gantt_pill d-flex align-items-center">
|
||||||
|
<div class="me-2">
|
||||||
|
<t t-if="task_priority == '1'">
|
||||||
|
<i class="fa fa-exclamation text-warning"/>
|
||||||
|
</t>
|
||||||
|
<t t-elif="task_priority == '2'">
|
||||||
|
<i class="fa fa-fire text-danger"/>
|
||||||
|
</t>
|
||||||
|
<t t-if="is_completed">
|
||||||
|
<i class="fa fa-check-circle text-success"/>
|
||||||
|
</t>
|
||||||
|
<t t-if="show_approval_flow and performance_icon and not is_completed">
|
||||||
|
<i t-attf-class="fa {{performance_icon}}"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<t t-esc="name"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</templates>
|
||||||
|
|
||||||
|
<field name="project_id"/>
|
||||||
|
<field name="allow_milestones"/>
|
||||||
|
<field name="milestone_id"/>
|
||||||
|
<field name="user_ids"/>
|
||||||
|
<field name="user_names"/>
|
||||||
|
<field name="partner_id"/>
|
||||||
|
<field name="planning_overlap"/>
|
||||||
|
<field name="allocated_hours"/>
|
||||||
|
<field name="gantt_color"/>
|
||||||
|
<field name="gantt_user_id"/>
|
||||||
|
<field name="performance_icon"/>
|
||||||
|
<field name="show_approval_flow"/>
|
||||||
|
<field name="task_priority"/>
|
||||||
|
<field name="current_stage_performance"/>
|
||||||
|
<field name="estimated_hours"/>
|
||||||
|
<field name="actual_hours"/>
|
||||||
|
<field name="is_overdue"/>
|
||||||
|
<field name="gantt_date_start"/>
|
||||||
|
<field name="planned_date_begin"/>
|
||||||
|
<field name="date_deadline"/>
|
||||||
|
<field name="formatted_start_date"/>
|
||||||
|
<field name="formatted_end_date"/>
|
||||||
|
<field name="state"/>
|
||||||
|
<field name="progress"/>
|
||||||
|
<field name="is_completed"/>
|
||||||
|
<field name="days_remaining"/>
|
||||||
|
</gantt>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<!-- <record id="project_gantt.project_task_action_from_partner_gantt_view" model="ir.actions.act_window.view">-->
|
||||||
|
<!-- <field name="view_mode">gantt</field>-->
|
||||||
|
<!-- <field name="act_window_id" ref="project.project_task_action_from_partner"/>-->
|
||||||
|
<!-- <field name="view_id" ref="project_task_custom_gantt_view"/>-->
|
||||||
|
<!-- </record>-->
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="hr_timesheet_line_tree_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.timesheet.tree.inherit</field>
|
||||||
|
<field name="model">account.analytic.line</field>
|
||||||
|
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='name']" position="after">
|
||||||
|
<field name="stage_id" required="0" optional="show" readonly="readonly_timesheet"
|
||||||
|
options="{'no_create': True,'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_timesheet_line_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.timesheet.form.inherit</field>
|
||||||
|
<field name="model">account.analytic.line</field>
|
||||||
|
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='task_id']" position="after">
|
||||||
|
<field name="stage_id" required="0" optional="show" readonly="readonly_timesheet"
|
||||||
|
options="{'no_create': True,'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,344 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<!-- 1. list VIEW -->
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<record id="view_user_task_availability_list" model="ir.ui.view">
|
||||||
|
<field name="name">user.task.availability.list</field>
|
||||||
|
<field name="model">user.task.availability</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="User Task Availability" create="false" edit="false" delete="false">
|
||||||
|
<field name="user_id"/>
|
||||||
|
<field name="task_id"/>
|
||||||
|
<field name="is_generic"/>
|
||||||
|
<field name="project_id"/>
|
||||||
|
<field name="status_label"/>
|
||||||
|
<field name="work_start_datetime"/>
|
||||||
|
<field name="work_end_datetime"/>
|
||||||
|
<field name="estimated_hours"/>
|
||||||
|
<field name="actual_hours" optional="hide"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<!-- 2. SEARCH FILTERS -->
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<record id="view_user_task_availability_search" model="ir.ui.view">
|
||||||
|
<field name="name">user.task.availability.search</field>
|
||||||
|
<field name="model">user.task.availability</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Task Availability">
|
||||||
|
<field name="task_sequence"/>
|
||||||
|
<!-- FILTER BY STATE -->
|
||||||
|
<filter name="state_in_progress"
|
||||||
|
string="In Progress"
|
||||||
|
domain="[('state','=','01_in_progress')]"/>
|
||||||
|
<filter name="state_closed"
|
||||||
|
string="Closed"
|
||||||
|
domain="[('state','in',['1_done','1_canceled'])]"/>
|
||||||
|
|
||||||
|
<!-- FILTER BY USER -->
|
||||||
|
<filter name="user_me"
|
||||||
|
string="My Tasks"
|
||||||
|
domain="[('user_id','=',uid)]"/>
|
||||||
|
<filter name="is_generic_task"
|
||||||
|
string="Generic Tasks"
|
||||||
|
domain="[('is_generic','=',True)]"/>
|
||||||
|
<filter name="non_generic_task"
|
||||||
|
string="Non-Generic Tasks"
|
||||||
|
domain="[('is_generic','=',False)]"/>
|
||||||
|
|
||||||
|
<!-- GROUP BY USER, STATE, PROJECT -->
|
||||||
|
<group expand="0" string="Group By">
|
||||||
|
<filter name="group_user" string="User" context="{'group_by':'user_id'}"/>
|
||||||
|
<filter name="group_project" string="Project" context="{'group_by':'project_id'}"/>
|
||||||
|
<filter name="group_state" string="State" context="{'group_by':'state'}"/>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<!-- DATE RANGE -->
|
||||||
|
<group expand="0" string="Date">
|
||||||
|
<filter string="Today"
|
||||||
|
name="today"
|
||||||
|
domain="[('work_start_datetime','>=',context_today())]"/>
|
||||||
|
|
||||||
|
<filter string="This Week"
|
||||||
|
name="week"
|
||||||
|
domain="['&',('work_start_datetime','>=',context_today()),('work_start_datetime','<=',(context_today() + datetime.timedelta(days=7)))]"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<!-- 3. CALENDAR VIEW -->
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<record id="view_user_task_availability_calendar" model="ir.ui.view">
|
||||||
|
<field name="name">user.task.availability.calendar</field>
|
||||||
|
<field name="model">user.task.availability</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<calendar string="Task Calendar"
|
||||||
|
date_start="work_start_datetime"
|
||||||
|
date_stop="work_end_datetime"
|
||||||
|
color="user_id"
|
||||||
|
create="false">
|
||||||
|
<field name="task_id"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<field name="project_id"/>
|
||||||
|
<field name="status_label"/>
|
||||||
|
</calendar>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<!-- 4. GANTT VIEW -->
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<record id="view_user_task_availability_gantt" model="ir.ui.view">
|
||||||
|
<field name="name">user.task.availability.gantt</field>
|
||||||
|
<field name="model">user.task.availability</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<gantt string="User Workload Gantt"
|
||||||
|
date_start="work_start_datetime"
|
||||||
|
date_stop="work_end_datetime"
|
||||||
|
default_scale="week"
|
||||||
|
scales="day,week,month,year"
|
||||||
|
color="task_status_color"
|
||||||
|
default_group_by="user_id"
|
||||||
|
progress_bar="progress_percentage"
|
||||||
|
pill_label="True"
|
||||||
|
total_row="True"
|
||||||
|
decoration-success="state == '1_done'"
|
||||||
|
decoration-danger="state == '1_canceled'"
|
||||||
|
|
||||||
|
decoration-warning="is_overdue"
|
||||||
|
decoration-info="is_future"
|
||||||
|
js_class="task_gantt"
|
||||||
|
display_unavailability="1"
|
||||||
|
precision="{'day': 'hour:quarter', 'week': 'day:half', 'month': 'day:half'}">
|
||||||
|
|
||||||
|
<templates>
|
||||||
|
<div t-name="gantt-popover"
|
||||||
|
style="min-width:300px; max-width:340px; padding:16px 20px 16px 16px; border-radius:6px;">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HEADER -->
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
|
||||||
|
<div>
|
||||||
|
<div style="font-size:16px; font-weight:600;" t-esc="task_name"/>
|
||||||
|
<t t-if="progress_emoji">
|
||||||
|
<span class="me-1" t-esc="progress_emoji"/>
|
||||||
|
</t>
|
||||||
|
<!-- <div style="font-size:12px; color:#888;" t-esc="task_id[1]"/>-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span t-if="state == '1_done'"
|
||||||
|
style="background:#d4edda; color:#155724; padding:2px 6px; border-radius:4px; font-size:11px;">
|
||||||
|
✔ Done
|
||||||
|
</span>
|
||||||
|
<span t-if="is_overdue"
|
||||||
|
style="background:#f8d7da; color:#721c24; padding:2px 6px; border-radius:4px; font-size:11px;">
|
||||||
|
⚠ Overdue
|
||||||
|
</span>
|
||||||
|
<span t-if="is_future"
|
||||||
|
style="background:#d1ecf1; color:#0c5460; padding:2px 6px; border-radius:4px; font-size:11px;">
|
||||||
|
⏱ Scheduled
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- GRID (NO BOOTSTRAP) -->
|
||||||
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:10px; font-size:13px;">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style="color:#888;">Assigned To</div>
|
||||||
|
<div style="font-weight:600;" t-esc="user_id[1]"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style="color:#888;">Project</div>
|
||||||
|
<div style="font-weight:600;" t-esc="project_id[1]"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style="color:#888;">Stage</div>
|
||||||
|
<div style="font-weight:600;" t-esc="stage_id[1]"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style="color:#888;">Status</div>
|
||||||
|
<div style="font-weight:600;" t-esc="status_label"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="border-top:1px solid #eee; margin:14px 0;"></div>
|
||||||
|
|
||||||
|
<!-- TIME SECTION -->
|
||||||
|
<div style="display:flex; justify-content:space-between; font-size:13px;">
|
||||||
|
<div>
|
||||||
|
<div style="color:#888; margin-bottom:2px;">📅 Start</div>
|
||||||
|
<div style="font-weight:600;"
|
||||||
|
t-esc="work_start_datetime.toFormat('MMM dd, yyyy HH:mm')"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align:right;">
|
||||||
|
<div style="color:#888; margin-bottom:2px;">🏁 End</div>
|
||||||
|
<div style="font-weight:600;" t-esc="work_end_datetime.toFormat('MMM dd, yyyy HH:mm')"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="border-top:1px solid #eee; margin:14px 0;"></div>
|
||||||
|
|
||||||
|
<!-- PROGRESS -->
|
||||||
|
<div>
|
||||||
|
<div style="display:flex; justify-content:space-between; font-size:13px; margin-bottom:5px;">
|
||||||
|
<span style="color:#888;">Progress</span>
|
||||||
|
<span style="font-weight:600;"><t t-esc="progress_percentage"/>%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div style="background:#eee; border-radius:4px; height:6px; width:100%; margin-top:4px;">-->
|
||||||
|
<!-- <div style="background:#4c8bf5; height:6px; border-radius:4px;"-->
|
||||||
|
<!-- t-att-style="'width: %s%% !important' % progress_percentage">-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="border-top:1px solid #eee; margin:14px 0;"></div>
|
||||||
|
|
||||||
|
<!-- FOOTER -->
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; font-size:13px;">
|
||||||
|
<div>
|
||||||
|
<span style="color:#888;">Source:</span>
|
||||||
|
<span style="background:#f1f1f1; padding:2px 6px; border-radius:4px;"
|
||||||
|
t-esc="source_type"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="gantt-pulse">
|
||||||
|
<div class="o_gantt_pill d-flex align-items-center shadow-sm">
|
||||||
|
<div class="me-2 d-flex align-items-center">
|
||||||
|
<t t-if="progress_emoji">
|
||||||
|
<span class="me-1" t-esc="progress_emoji"/>
|
||||||
|
</t>
|
||||||
|
<t t-if="state == '1_done'">
|
||||||
|
<i class="fa fa-check-circle text-success me-1"/>
|
||||||
|
</t>
|
||||||
|
<t t-if="is_overdue">
|
||||||
|
<i class="fa fa-exclamation-triangle text-danger me-1"/>
|
||||||
|
</t>
|
||||||
|
<t t-if="is_future">
|
||||||
|
<i class="fa fa-clock-o text-info me-1"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<div class="fw-bold" t-esc="task_name"/>
|
||||||
|
<div class="small text-muted">
|
||||||
|
<span t-esc="estimated_hours" widget="timesheet_uom_no_toggle"/>h /
|
||||||
|
<span t-esc="actual_hours" widget="timesheet_uom_no_toggle"/>h
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ms-2">
|
||||||
|
<div class="progress" style="height: 5px; width: 60px;">
|
||||||
|
<div class="progress-bar" role="progressbar"
|
||||||
|
t-attf-style="width: {{progress_percentage}}%"
|
||||||
|
t-att-aria-valuenow="progress_percentage"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</templates>
|
||||||
|
|
||||||
|
<field name="task_id"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<field name="project_id"/>
|
||||||
|
<field name="stage_id"/>
|
||||||
|
<field name="task_name"/>
|
||||||
|
<field name="status_label"/>
|
||||||
|
<field name="work_start_datetime"/>
|
||||||
|
<field name="work_end_datetime"/>
|
||||||
|
<field name="estimated_hours"/>
|
||||||
|
<field name="actual_hours"/>
|
||||||
|
<field name="progress_percentage"/>
|
||||||
|
<field name="progress_emoji"/>
|
||||||
|
<field name="task_status_color"/>
|
||||||
|
<field name="state"/>
|
||||||
|
<field name="is_overdue"/>
|
||||||
|
<field name="is_future"/>
|
||||||
|
<field name="source_type"/>
|
||||||
|
</gantt>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<!-- 5. KANBAN VIEW -->
|
||||||
|
<!-- “Who is Working Now?” -->
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<record id="view_user_task_availability_kanban" model="ir.ui.view">
|
||||||
|
<field name="name">user.task.availability.kanban</field>
|
||||||
|
<field name="model">user.task.availability</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<kanban class="o_kanban_mobile" create="false" edit="false">
|
||||||
|
<field name="user_id"/>
|
||||||
|
<field name="task_id"/>
|
||||||
|
<field name="project_id"/>
|
||||||
|
<field name="status_label"/>
|
||||||
|
<field name="work_start_datetime"/>
|
||||||
|
<field name="work_end_datetime"/>
|
||||||
|
|
||||||
|
<templates>
|
||||||
|
<t t-name="kanban-box">
|
||||||
|
<div class="oe_kanban_card">
|
||||||
|
<div class="o_kanban_primary_left">
|
||||||
|
<strong>
|
||||||
|
<field name="user_id"/>
|
||||||
|
</strong>
|
||||||
|
<div>
|
||||||
|
<field name="task_id"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Status:</span>
|
||||||
|
<field name="status_label"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>From:</span>
|
||||||
|
<field name="work_start_datetime"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>To:</span>
|
||||||
|
<field name="work_end_datetime"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
|
||||||
|
</kanban>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<!-- 6. ACTION & MENU -->
|
||||||
|
<!-- ===================================================== -->
|
||||||
|
<record id="action_user_task_availability" model="ir.actions.act_window">
|
||||||
|
<field name="name">User Task Availability</field>
|
||||||
|
<field name="res_model">user.task.availability</field>
|
||||||
|
<field name="view_mode">list,kanban,calendar,gantt</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
name="User Task Availability"
|
||||||
|
id="menu_user_task_availability"
|
||||||
|
action="action_user_task_availability"
|
||||||
|
parent="project.menu_project_report"
|
||||||
|
sequence="10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue