[MOV] hr_timesheet: copy/move files per model
In this commit, we just copy/move the files. In next commit, delete the part of each file that is not related to its model. task-3244966 Part-of: odoo/odoo#116256
This commit is contained in:
@@ -28,18 +28,21 @@ up a management by affair.
|
||||
'data/digest_data.xml',
|
||||
'views/hr_timesheet_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/project_views.xml',
|
||||
'views/project_portal_templates.xml',
|
||||
'views/project_project_views.xml',
|
||||
'views/project_task_views.xml',
|
||||
'views/project_task_portal_templates.xml',
|
||||
'views/hr_timesheet_portal_templates.xml',
|
||||
'report/hr_timesheet_report_view.xml',
|
||||
'report/project_report_view.xml',
|
||||
'report/report_timesheet_templates.xml',
|
||||
'views/hr_views.xml',
|
||||
'views/hr_department_views.xml',
|
||||
'views/hr_employee_views.xml',
|
||||
'data/hr_timesheet_data.xml',
|
||||
'views/project_sharing_views.xml',
|
||||
'views/project_task_sharing_views.xml',
|
||||
'views/rating_rating_views.xml',
|
||||
'views/project_update_views.xml',
|
||||
'wizard/hr_employee_delete_wizard_views.xml',
|
||||
'views/hr_timesheet_menus.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/hr_timesheet_demo.xml',
|
||||
|
||||
@@ -7,6 +7,7 @@ from . import ir_http
|
||||
from . import ir_ui_menu
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
from . import project
|
||||
from . import project_project
|
||||
from . import project_task
|
||||
from . import project_collaborator
|
||||
from . import uom
|
||||
from . import uom_uom
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import defaultdict
|
||||
from lxml import etree
|
||||
import re
|
||||
|
||||
from odoo import api, Command, fields, models, _, _lt
|
||||
from odoo import api, fields, models, _, _lt
|
||||
from odoo.exceptions import UserError, AccessError, ValidationError
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError, ValidationError, RedirectWarning
|
||||
from odoo.addons.rating.models.rating_data import OPERATOR_MAPPING
|
||||
|
||||
PROJECT_TASK_READABLE_FIELDS = {
|
||||
'allow_subtasks',
|
||||
'allow_timesheets',
|
||||
'analytic_account_active',
|
||||
'effective_hours',
|
||||
'encode_uom_in_days',
|
||||
'planned_hours',
|
||||
'progress',
|
||||
'overtime',
|
||||
'remaining_hours',
|
||||
'subtask_effective_hours',
|
||||
'subtask_planned_hours',
|
||||
'timesheet_ids',
|
||||
'total_hours_spent',
|
||||
}
|
||||
|
||||
class Project(models.Model):
|
||||
_inherit = "project.project"
|
||||
|
||||
allow_timesheets = fields.Boolean(
|
||||
"Timesheets", compute='_compute_allow_timesheets', store=True, readonly=False,
|
||||
default=True)
|
||||
analytic_account_id = fields.Many2one(
|
||||
# note: replaces ['|', ('company_id', '=', False), ('company_id', '=', company_id)]
|
||||
domain="""[
|
||||
'|', ('company_id', '=', False), ('company_id', '=', company_id),
|
||||
('partner_id', '=?', partner_id),
|
||||
]"""
|
||||
)
|
||||
|
||||
timesheet_ids = fields.One2many('account.analytic.line', 'project_id', 'Associated Timesheets')
|
||||
timesheet_count = fields.Integer(compute="_compute_timesheet_count", groups='hr_timesheet.group_hr_timesheet_user')
|
||||
timesheet_encode_uom_id = fields.Many2one('uom.uom', related='company_id.timesheet_encode_uom_id')
|
||||
total_timesheet_time = fields.Integer(
|
||||
compute='_compute_total_timesheet_time', groups='hr_timesheet.group_hr_timesheet_user',
|
||||
help="Total number of time (in the proper UoM) recorded in the project, rounded to the unit.")
|
||||
encode_uom_in_days = fields.Boolean(compute='_compute_encode_uom_in_days')
|
||||
is_internal_project = fields.Boolean(compute='_compute_is_internal_project', search='_search_is_internal_project')
|
||||
remaining_hours = fields.Float(compute='_compute_remaining_hours', string='Remaining Invoiced Time', compute_sudo=True)
|
||||
is_project_overtime = fields.Boolean('Project in Overtime', compute='_compute_remaining_hours', search='_search_is_project_overtime', compute_sudo=True)
|
||||
allocated_hours = fields.Float(string='Allocated Hours')
|
||||
|
||||
def _compute_encode_uom_in_days(self):
|
||||
self.encode_uom_in_days = self.env.company.timesheet_encode_uom_id == self.env.ref('uom.product_uom_day')
|
||||
|
||||
@api.depends('analytic_account_id')
|
||||
def _compute_allow_timesheets(self):
|
||||
without_account = self.filtered(lambda t: not t.analytic_account_id and t._origin)
|
||||
without_account.update({'allow_timesheets': False})
|
||||
|
||||
@api.depends('company_id')
|
||||
def _compute_is_internal_project(self):
|
||||
for project in self:
|
||||
project.is_internal_project = project == project.company_id.internal_project_id
|
||||
|
||||
@api.model
|
||||
def _search_is_internal_project(self, operator, value):
|
||||
if not isinstance(value, bool):
|
||||
raise ValueError(_('Invalid value: %s', value))
|
||||
if operator not in ['=', '!=']:
|
||||
raise ValueError(_('Invalid operator: %s', operator))
|
||||
|
||||
query = """
|
||||
SELECT C.internal_project_id
|
||||
FROM res_company C
|
||||
WHERE C.internal_project_id IS NOT NULL
|
||||
"""
|
||||
if (operator == '=' and value is True) or (operator == '!=' and value is False):
|
||||
operator_new = 'inselect'
|
||||
else:
|
||||
operator_new = 'not inselect'
|
||||
return [('id', operator_new, (query, ()))]
|
||||
|
||||
@api.model
|
||||
def _get_view_cache_key(self, view_id=None, view_type='form', **options):
|
||||
"""The override of _get_view changing the time field labels according to the company timesheet encoding UOM
|
||||
makes the view cache dependent on the company timesheet encoding uom"""
|
||||
key = super()._get_view_cache_key(view_id, view_type, **options)
|
||||
return key + (self.env.company.timesheet_encode_uom_id,)
|
||||
|
||||
@api.model
|
||||
def _get_view(self, view_id=None, view_type='form', **options):
|
||||
arch, view = super()._get_view(view_id, view_type, **options)
|
||||
if view_type in ['tree', 'form'] and self.env.company.timesheet_encode_uom_id == self.env.ref('uom.product_uom_day'):
|
||||
arch = self.env['account.analytic.line']._apply_time_label(arch, related_model=self._name)
|
||||
return arch, view
|
||||
|
||||
@api.depends('allow_timesheets', 'timesheet_ids')
|
||||
def _compute_remaining_hours(self):
|
||||
timesheets_read_group = self.env['account.analytic.line']._read_group(
|
||||
[('project_id', 'in', self.ids)],
|
||||
['project_id', 'unit_amount'],
|
||||
['project_id'],
|
||||
lazy=False)
|
||||
timesheet_time_dict = {res['project_id'][0]: res['unit_amount'] for res in timesheets_read_group}
|
||||
for project in self:
|
||||
project.remaining_hours = project.allocated_hours - timesheet_time_dict.get(project.id, 0)
|
||||
project.is_project_overtime = project.remaining_hours < 0
|
||||
|
||||
@api.model
|
||||
def _search_is_project_overtime(self, operator, value):
|
||||
if not isinstance(value, bool):
|
||||
raise ValueError(_('Invalid value: %s') % value)
|
||||
if operator not in ['=', '!=']:
|
||||
raise ValueError(_('Invalid operator: %s') % operator)
|
||||
|
||||
query = """
|
||||
SELECT P.id
|
||||
FROM project_project P
|
||||
LEFT JOIN project_task T ON P.id = T.project_id
|
||||
WHERE p.allocated_hours != 0 AND p.allow_timesheets
|
||||
GROUP BY P.id
|
||||
HAVING P.allocated_hours - SUM(T.effective_hours) < 0
|
||||
"""
|
||||
if (operator == '=' and value is True) or (operator == '!=' and value is False):
|
||||
operator_new = 'inselect'
|
||||
else:
|
||||
operator_new = 'not inselect'
|
||||
return [('id', operator_new, (query, ()))]
|
||||
|
||||
@api.constrains('allow_timesheets', 'analytic_account_id')
|
||||
def _check_allow_timesheet(self):
|
||||
for project in self:
|
||||
if project.allow_timesheets and not project.analytic_account_id:
|
||||
raise ValidationError(_('You cannot use timesheets without an analytic account.'))
|
||||
|
||||
@api.depends('timesheet_ids')
|
||||
def _compute_total_timesheet_time(self):
|
||||
timesheets_read_group = self.env['account.analytic.line'].read_group(
|
||||
[('project_id', 'in', self.ids)],
|
||||
['project_id', 'unit_amount', 'product_uom_id'],
|
||||
['project_id', 'product_uom_id'],
|
||||
lazy=False)
|
||||
timesheet_time_dict = defaultdict(list)
|
||||
uom_ids = set(self.timesheet_encode_uom_id.ids)
|
||||
|
||||
for result in timesheets_read_group:
|
||||
uom_id = result['product_uom_id'] and result['product_uom_id'][0]
|
||||
if uom_id:
|
||||
uom_ids.add(uom_id)
|
||||
timesheet_time_dict[result['project_id'][0]].append((uom_id, result['unit_amount']))
|
||||
|
||||
uoms_dict = {uom.id: uom for uom in self.env['uom.uom'].browse(uom_ids)}
|
||||
for project in self:
|
||||
# Timesheets may be stored in a different unit of measure, so first
|
||||
# we convert all of them to the reference unit
|
||||
# if the timesheet has no product_uom_id then we take the one of the project
|
||||
total_time = 0.0
|
||||
for product_uom_id, unit_amount in timesheet_time_dict[project.id]:
|
||||
factor = uoms_dict.get(product_uom_id, project.timesheet_encode_uom_id).factor_inv
|
||||
total_time += unit_amount * (1.0 if project.encode_uom_in_days else factor)
|
||||
# Now convert to the proper unit of measure set in the settings
|
||||
total_time *= project.timesheet_encode_uom_id.factor
|
||||
project.total_timesheet_time = int(round(total_time))
|
||||
|
||||
@api.depends('timesheet_ids')
|
||||
def _compute_timesheet_count(self):
|
||||
timesheet_read_group = self.env['account.analytic.line']._read_group(
|
||||
[('project_id', 'in', self.ids)],
|
||||
['project_id'],
|
||||
['project_id']
|
||||
)
|
||||
timesheet_project_map = {project_info['project_id'][0]: project_info['project_id_count'] for project_info in timesheet_read_group}
|
||||
for project in self:
|
||||
project.timesheet_count = timesheet_project_map.get(project.id, 0)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
""" Create an analytic account if project allow timesheet and don't provide one
|
||||
Note: create it before calling super() to avoid raising the ValidationError from _check_allow_timesheet
|
||||
"""
|
||||
defaults = self.default_get(['allow_timesheets', 'analytic_account_id'])
|
||||
for vals in vals_list:
|
||||
allow_timesheets = vals.get('allow_timesheets', defaults.get('allow_timesheets'))
|
||||
analytic_account_id = vals.get('analytic_account_id', defaults.get('analytic_account_id'))
|
||||
if allow_timesheets and not analytic_account_id:
|
||||
analytic_account = self._create_analytic_account_from_values(vals)
|
||||
vals['analytic_account_id'] = analytic_account.id
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, values):
|
||||
# create the AA for project still allowing timesheet
|
||||
if values.get('allow_timesheets') and not values.get('analytic_account_id'):
|
||||
for project in self:
|
||||
if not project.analytic_account_id:
|
||||
project._create_analytic_account()
|
||||
return super(Project, self).write(values)
|
||||
|
||||
def name_get(self):
|
||||
res = super().name_get()
|
||||
if len(self.env.context.get('allowed_company_ids', [])) <= 1:
|
||||
return res
|
||||
name_mapping = dict(res)
|
||||
for project in self:
|
||||
if project.is_internal_project:
|
||||
name_mapping[project.id] = f'{name_mapping[project.id]} - {project.company_id.name}'
|
||||
return list(name_mapping.items())
|
||||
|
||||
@api.model
|
||||
def _init_data_analytic_account(self):
|
||||
self.search([('analytic_account_id', '=', False), ('allow_timesheets', '=', True)])._create_analytic_account()
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_except_contains_entries(self):
|
||||
"""
|
||||
If some projects to unlink have some timesheets entries, these
|
||||
timesheets entries must be unlinked first.
|
||||
In this case, a warning message is displayed through a RedirectWarning
|
||||
and allows the user to see timesheets entries to unlink.
|
||||
"""
|
||||
projects_with_timesheets = self.filtered(lambda p: p.timesheet_ids)
|
||||
if projects_with_timesheets:
|
||||
if len(projects_with_timesheets) > 1:
|
||||
warning_msg = _("These projects have some timesheet entries referencing them. Before removing these projects, you have to remove these timesheet entries.")
|
||||
else:
|
||||
warning_msg = _("This project has some timesheet entries referencing it. Before removing this project, you have to remove these timesheet entries.")
|
||||
raise RedirectWarning(
|
||||
warning_msg, self.env.ref('hr_timesheet.timesheet_action_project').id,
|
||||
_('See timesheet entries'), {'active_ids': projects_with_timesheets.ids})
|
||||
|
||||
def _convert_project_uom_to_timesheet_encode_uom(self, time):
|
||||
uom_from = self.company_id.project_time_mode_id
|
||||
uom_to = self.env.company.timesheet_encode_uom_id
|
||||
return round(uom_from._compute_quantity(time, uom_to, raise_if_failure=False), 2)
|
||||
|
||||
|
||||
class Task(models.Model):
|
||||
_name = "project.task"
|
||||
_inherit = "project.task"
|
||||
|
||||
project_id = fields.Many2one(domain=[('is_internal_project', '=', False)])
|
||||
display_project_id = fields.Many2one(domain=[('is_internal_project', '=', False)])
|
||||
analytic_account_active = fields.Boolean("Active Analytic Account", compute='_compute_analytic_account_active', compute_sudo=True)
|
||||
allow_timesheets = fields.Boolean("Allow timesheets", related='project_id.allow_timesheets', help="Timesheets can be logged on this task.", readonly=True)
|
||||
remaining_hours = fields.Float("Remaining Hours", compute='_compute_remaining_hours', store=True, readonly=True, help="Number of allocated hours minus the number of hours spent.")
|
||||
remaining_hours_percentage = fields.Float(compute='_compute_remaining_hours_percentage', search='_search_remaining_hours_percentage')
|
||||
effective_hours = fields.Float("Hours Spent", compute='_compute_effective_hours', compute_sudo=True, store=True)
|
||||
total_hours_spent = fields.Float("Total Hours", compute='_compute_total_hours_spent', store=True, help="Time spent on this task and its sub-tasks (and their own sub-tasks).")
|
||||
progress = fields.Float("Progress", compute='_compute_progress_hours', store=True, group_operator="avg")
|
||||
overtime = fields.Float(compute='_compute_progress_hours', store=True)
|
||||
subtask_effective_hours = fields.Float("Hours Spent on Sub-Tasks", compute='_compute_subtask_effective_hours', recursive=True, store=True, help="Time spent on the sub-tasks (and their own sub-tasks) of this task.")
|
||||
timesheet_ids = fields.One2many('account.analytic.line', 'task_id', 'Timesheets')
|
||||
encode_uom_in_days = fields.Boolean(compute='_compute_encode_uom_in_days', default=lambda self: self._uom_in_days())
|
||||
display_name = fields.Char(help="""Use these keywords in the title to set new tasks:\n
|
||||
30h Allocate 30 hours to the task
|
||||
#tags Set tags on the task
|
||||
@user Assign the task to a user
|
||||
! Set the task a high priority\n
|
||||
Make sure to use the right format and order e.g. Improve the configuration screen 5h #feature #v16 @Mitchell !""",
|
||||
)
|
||||
@property
|
||||
def SELF_READABLE_FIELDS(self):
|
||||
return super().SELF_READABLE_FIELDS | PROJECT_TASK_READABLE_FIELDS
|
||||
|
||||
def _uom_in_days(self):
|
||||
return self.env.company.timesheet_encode_uom_id == self.env.ref('uom.product_uom_day')
|
||||
|
||||
def _compute_encode_uom_in_days(self):
|
||||
self.encode_uom_in_days = self._uom_in_days()
|
||||
|
||||
@api.depends('analytic_account_id.active', 'project_id.analytic_account_id.active')
|
||||
def _compute_analytic_account_active(self):
|
||||
""" Overridden in sale_timesheet """
|
||||
for task in self:
|
||||
task.analytic_account_active = task._get_task_analytic_account_id().active
|
||||
|
||||
@api.depends('timesheet_ids.unit_amount')
|
||||
def _compute_effective_hours(self):
|
||||
if not any(self._ids):
|
||||
for task in self:
|
||||
task.effective_hours = round(sum(task.timesheet_ids.mapped('unit_amount')), 2)
|
||||
return
|
||||
timesheet_read_group = self.env['account.analytic.line'].read_group([('task_id', 'in', self.ids)], ['unit_amount', 'task_id'], ['task_id'])
|
||||
timesheets_per_task = {res['task_id'][0]: res['unit_amount'] for res in timesheet_read_group}
|
||||
for task in self:
|
||||
task.effective_hours = round(timesheets_per_task.get(task.id, 0.0), 2)
|
||||
|
||||
@api.depends('effective_hours', 'subtask_effective_hours', 'planned_hours')
|
||||
def _compute_progress_hours(self):
|
||||
for task in self:
|
||||
if (task.planned_hours > 0.0):
|
||||
task_total_hours = task.effective_hours + task.subtask_effective_hours
|
||||
task.overtime = max(task_total_hours - task.planned_hours, 0)
|
||||
if task_total_hours > task.planned_hours:
|
||||
task.progress = 100
|
||||
else:
|
||||
task.progress = round(100.0 * task_total_hours / task.planned_hours, 2)
|
||||
else:
|
||||
task.progress = 0.0
|
||||
task.overtime = 0
|
||||
|
||||
@api.depends('planned_hours', 'remaining_hours')
|
||||
def _compute_remaining_hours_percentage(self):
|
||||
for task in self:
|
||||
if task.planned_hours > 0.0:
|
||||
task.remaining_hours_percentage = task.remaining_hours / task.planned_hours
|
||||
else:
|
||||
task.remaining_hours_percentage = 0.0
|
||||
|
||||
def _search_remaining_hours_percentage(self, operator, value):
|
||||
if operator not in OPERATOR_MAPPING:
|
||||
raise NotImplementedError(_('This operator %s is not supported in this search method.', operator))
|
||||
query = f"""
|
||||
SELECT id
|
||||
FROM {self._table}
|
||||
WHERE remaining_hours > 0
|
||||
AND planned_hours > 0
|
||||
AND remaining_hours / planned_hours {operator} %s
|
||||
"""
|
||||
return [('id', 'inselect', (query, (value,)))]
|
||||
|
||||
@api.depends('effective_hours', 'subtask_effective_hours', 'planned_hours')
|
||||
def _compute_remaining_hours(self):
|
||||
for task in self:
|
||||
task.remaining_hours = task.planned_hours - task.effective_hours - task.subtask_effective_hours
|
||||
|
||||
@api.depends('effective_hours', 'subtask_effective_hours')
|
||||
def _compute_total_hours_spent(self):
|
||||
for task in self:
|
||||
task.total_hours_spent = task.effective_hours + task.subtask_effective_hours
|
||||
|
||||
@api.depends('child_ids.effective_hours', 'child_ids.subtask_effective_hours')
|
||||
def _compute_subtask_effective_hours(self):
|
||||
for task in self.with_context(active_test=False):
|
||||
task.subtask_effective_hours = sum(child_task.effective_hours + child_task.subtask_effective_hours for child_task in task.child_ids)
|
||||
|
||||
def _get_group_pattern(self):
|
||||
return {
|
||||
**super()._get_group_pattern(),
|
||||
'planned_hours': r'\s(\d+(?:\.\d+)?)[hH]',
|
||||
}
|
||||
|
||||
def _get_groups_patterns(self):
|
||||
return ['(?:%s)*' % self._get_group_pattern()['planned_hours']] + super()._get_groups_patterns()
|
||||
|
||||
def _get_cannot_start_with_patterns(self):
|
||||
return super()._get_cannot_start_with_patterns() + [r'(?!\d+(?:\.\d+)?(?:h|H))']
|
||||
|
||||
def _extract_planned_hours(self):
|
||||
planned_hours_group = self._get_group_pattern()['planned_hours']
|
||||
if self.allow_timesheets:
|
||||
self.planned_hours = sum(float(num) for num in re.findall(planned_hours_group, self.display_name))
|
||||
self.display_name, dummy = re.subn(planned_hours_group, '', self.display_name)
|
||||
|
||||
def _get_groups(self):
|
||||
return [lambda task: task._extract_planned_hours()] + super()._get_groups()
|
||||
|
||||
def action_view_subtask_timesheet(self):
|
||||
self.ensure_one()
|
||||
tasks = self.with_context(active_test=False)._get_all_subtasks()
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("hr_timesheet.timesheet_action_all")
|
||||
graph_view_id = self.env.ref("hr_timesheet.view_hr_timesheet_line_graph_by_employee").id
|
||||
new_views = []
|
||||
for view in action['views']:
|
||||
if view[1] == 'graph':
|
||||
view = (graph_view_id, 'graph')
|
||||
new_views.insert(0, view) if view[1] == 'tree' else new_views.append(view)
|
||||
action.update({
|
||||
'display_name': _('Timesheets'),
|
||||
'context': {'default_project_id': self.project_id.id, 'grid_range': 'week'},
|
||||
'domain': [('project_id', '!=', False), ('task_id', 'in', tasks.ids)],
|
||||
'views': new_views,
|
||||
})
|
||||
return action
|
||||
|
||||
def _get_timesheet(self):
|
||||
# Is override in sale_timesheet
|
||||
return self.timesheet_ids
|
||||
|
||||
def write(self, values):
|
||||
# a timesheet must have an analytic account (and a project)
|
||||
if 'project_id' in values and not values.get('project_id') and self._get_timesheet():
|
||||
raise UserError(_('This task must be part of a project because there are some timesheets linked to it.'))
|
||||
res = super(Task, self).write(values)
|
||||
|
||||
if 'project_id' in values:
|
||||
project = self.env['project.project'].browse(values.get('project_id'))
|
||||
if project.allow_timesheets:
|
||||
# We write on all non yet invoiced timesheet the new project_id (if project allow timesheet)
|
||||
self._get_timesheet().write({'project_id': values.get('project_id')})
|
||||
|
||||
return res
|
||||
|
||||
def name_get(self):
|
||||
if self.env.context.get('hr_timesheet_display_remaining_hours'):
|
||||
name_mapping = dict(super().name_get())
|
||||
for task in self:
|
||||
if task.allow_timesheets and task.planned_hours > 0 and task.encode_uom_in_days:
|
||||
days_left = _("(%s days remaining)") % task._convert_hours_to_days(task.remaining_hours)
|
||||
name_mapping[task.id] = name_mapping.get(task.id, '') + u"\u00A0" + days_left
|
||||
elif task.allow_timesheets and task.planned_hours > 0:
|
||||
hours, mins = (str(int(duration)).rjust(2, '0') for duration in divmod(abs(task.remaining_hours) * 60, 60))
|
||||
hours_left = _(
|
||||
"(%(sign)s%(hours)s:%(minutes)s remaining)",
|
||||
sign='-' if task.remaining_hours < 0 else '',
|
||||
hours=hours,
|
||||
minutes=mins,
|
||||
)
|
||||
name_mapping[task.id] = name_mapping.get(task.id, '') + u"\u00A0" + hours_left
|
||||
return list(name_mapping.items())
|
||||
return super().name_get()
|
||||
|
||||
@api.model
|
||||
def _get_view_cache_key(self, view_id=None, view_type='form', **options):
|
||||
"""The override of _get_view changing the time field labels according to the company timesheet encoding UOM
|
||||
makes the view cache dependent on the company timesheet encoding uom"""
|
||||
key = super()._get_view_cache_key(view_id, view_type, **options)
|
||||
return key + (self.env.company.timesheet_encode_uom_id,)
|
||||
|
||||
@api.model
|
||||
def _get_view(self, view_id=None, view_type='form', **options):
|
||||
""" Set the correct label for `unit_amount`, depending on company UoM """
|
||||
arch, view = super()._get_view(view_id, view_type, **options)
|
||||
# Use of sudo as the portal user doesn't have access to uom
|
||||
arch = self.env['account.analytic.line'].sudo()._apply_timesheet_label(arch)
|
||||
|
||||
if view_type in ['tree', 'pivot', 'graph', 'form'] and self.env.company.timesheet_encode_uom_id == self.env.ref('uom.product_uom_day'):
|
||||
arch = self.env['account.analytic.line']._apply_time_label(arch, related_model=self._name)
|
||||
|
||||
return arch, view
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_except_contains_entries(self):
|
||||
"""
|
||||
If some tasks to unlink have some timesheets entries, these
|
||||
timesheets entries must be unlinked first.
|
||||
In this case, a warning message is displayed through a RedirectWarning
|
||||
and allows the user to see timesheets entries to unlink.
|
||||
"""
|
||||
timesheet_data = self.env['account.analytic.line'].sudo()._read_group(
|
||||
[('task_id', 'in', self.ids)],
|
||||
['task_id'],
|
||||
['task_id'],
|
||||
)
|
||||
task_with_timesheets_ids = [res['task_id'][0] for res in timesheet_data]
|
||||
if task_with_timesheets_ids:
|
||||
if len(task_with_timesheets_ids) > 1:
|
||||
warning_msg = _("These tasks have some timesheet entries referencing them. Before removing these tasks, you have to remove these timesheet entries.")
|
||||
else:
|
||||
warning_msg = _("This task has some timesheet entries referencing it. Before removing this task, you have to remove these timesheet entries.")
|
||||
raise RedirectWarning(
|
||||
warning_msg, self.env.ref('hr_timesheet.timesheet_action_task').id,
|
||||
_('See timesheet entries'), {'active_ids': task_with_timesheets_ids})
|
||||
|
||||
@api.model
|
||||
def _convert_hours_to_days(self, time):
|
||||
uom_hour = self.env.ref('uom.product_uom_hour')
|
||||
uom_day = self.env.ref('uom.product_uom_day')
|
||||
return round(uom_hour._compute_quantity(time, uom_day, raise_if_failure=False), 2)
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="timesheet_action_from_employee" model="ir.actions.act_window">
|
||||
<field name="name">Timesheets</field>
|
||||
<field name="res_model">account.analytic.line</field>
|
||||
<field name="search_view_id" ref="hr_timesheet_line_search"/>
|
||||
<field name="domain">[('project_id', '!=', False), ('employee_id', '=', active_id)]</field>
|
||||
<field name="context">{
|
||||
'default_employee_id': active_id,
|
||||
"is_timesheet": 1,
|
||||
}</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_from_employee_list" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="5"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="hr_timesheet_line_tree"/>
|
||||
<field name="act_window_id" ref="timesheet_action_from_employee"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_from_employee_form" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="hr_timesheet_line_form"/>
|
||||
<field name="act_window_id" ref="timesheet_action_from_employee"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_view_form_inherit_timesheet" model="ir.ui.view">
|
||||
<field name="name">hr.employee.form.timesheet</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr_hourly_cost.view_employee_form"/>
|
||||
<field name="priority" eval="40"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/form" position="attributes">
|
||||
<attribute name="delete">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<field name="has_timesheet" groups="hr_timesheet.group_hr_timesheet_user" invisible="1"/>
|
||||
<button attrs="{'invisible': [('has_timesheet', '=', False)]}" class="oe_stat_button" type="object" name="action_timesheet_from_employee" icon="fa-calendar" groups="hr_timesheet.group_hr_timesheet_user">
|
||||
<div class="o_stat_info">
|
||||
<span class="o_stat_text">Timesheets</span>
|
||||
</div>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_employee_tree_inherit_timesheet" model="ir.ui.view">
|
||||
<field name="name">hr.employee.tree.timesheet</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/tree" position="attributes">
|
||||
<attribute name="delete">0</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_department_view_kanban" model="ir.ui.view">
|
||||
<field name="name">hr.department.kanban.inherit</field>
|
||||
<field name="model">hr.department</field>
|
||||
<field name="inherit_id" ref="hr.hr_department_view_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//div[hasclass('o_kanban_manage_reports')]" position="inside">
|
||||
<a name="%(act_hr_timesheet_report)d" type="action"
|
||||
groups="hr_timesheet.group_timesheet_manager" class="dropdown-item"
|
||||
context="{ 'search_default_department_id': [active_id], 'default_department_id': active_id}">
|
||||
Timesheets
|
||||
</a>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="unlink_employee_action" model="ir.actions.server">
|
||||
<field name="name">Delete</field>
|
||||
<field name="model_id" ref="hr.model_hr_employee"/>
|
||||
<field name="binding_model_id" ref="hr.model_hr_employee"/>
|
||||
<field name="binding_view_types">form,list</field>
|
||||
<field name="state">code</field>
|
||||
<field name="code">action = records.action_unlink_wizard()</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,432 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Timesheet root menus -->
|
||||
<menuitem id="timesheet_menu_root"
|
||||
name="Timesheets"
|
||||
sequence="75"
|
||||
groups="group_hr_timesheet_user"
|
||||
web_icon="hr_timesheet,static/description/icon_timesheet.png"/>
|
||||
|
||||
<menuitem id="menu_hr_time_tracking"
|
||||
name="Timesheets"
|
||||
parent="timesheet_menu_root"
|
||||
groups = "group_hr_timesheet_approver"
|
||||
sequence="5"/>
|
||||
|
||||
<!--
|
||||
Timesheet line Views
|
||||
-->
|
||||
<record id="hr_timesheet_line_tree" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.tree.hr_timesheet</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree editable="bottom" string="Timesheet Activities" sample="1">
|
||||
<field name="date"/>
|
||||
<field name="employee_id" invisible="1"/>
|
||||
<field name="project_id" required="1" options="{'no_create_edit': True, 'no_open': 1}"/>
|
||||
<field name="task_id" optional="show" options="{'no_create_edit': True, 'no_open': True}" widget="task_with_hours" context="{'default_project_id': project_id}"/>
|
||||
<field name="name" optional="show" required="0"/>
|
||||
<field name="unit_amount" optional="show" widget="timesheet_uom" sum="Total" decoration-danger="unit_amount > 24 or unit_amount < 0"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_view_tree_user" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.view.tree.with.user</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet_line_tree"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='employee_id']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
<attribute name="required">1</attribute>
|
||||
<attribute name="widget">many2one_avatar_employee</attribute>
|
||||
<attribute name="context">{'active_test': True}</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_timesheet_line_pivot" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.pivot</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Timesheets" sample="1">
|
||||
<field name="employee_id" type="row"/>
|
||||
<field name="date" interval="month" type="col"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
<field name="amount" string="Timesheet Costs"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_my_timesheet_line_pivot" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.pivot</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Timesheet" sample="1">
|
||||
<field name="date" interval="week" type="row"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
<field name="amount" string="Timesheet Costs"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_timesheet_line_graph" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.graph</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Timesheets" sample="1" js_class="hr_timesheet_graphview">
|
||||
<field name="task_id"/>
|
||||
<field name="project_id"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
<field name="amount" string="Timesheet Costs"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- For My Timesheet view, groups by week then project -->
|
||||
<record id="view_hr_timesheet_line_graph_my" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.graph</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Timesheet" sample="1" js_class="hr_timesheet_graphview">
|
||||
<field name="date" interval="week"/>
|
||||
<field name="project_id"/>
|
||||
<field name="amount" type="measure" string="Timesheet Costs"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- For Other Timesheet view, groups by employee then project -->
|
||||
<record id="view_hr_timesheet_line_graph_all" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.graph</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Timesheet" sample="1" js_class="hr_timesheet_graphview">
|
||||
<field name="employee_id"/>
|
||||
<field name="project_id"/>
|
||||
<field name="amount" type="measure" string="Timesheet Costs"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_timesheet_line_graph_by_employee" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.graph.by.employee</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.view_hr_timesheet_line_graph_all"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="project_id" position="replace"/>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_timesheet_line_form" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.form</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="priority">1</field>
|
||||
<field name="inherit_id" eval="False"/>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Analytic Entry">
|
||||
<sheet string="Analytic Entry">
|
||||
<group>
|
||||
<group>
|
||||
<field name="project_id" required="1"/>
|
||||
<field name="task_id" widget="task_with_hours" context="{'default_project_id': project_id}"/>
|
||||
<field name="name"/>
|
||||
<field name="company_id" groups="base.group_multi_company" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date"/>
|
||||
<field name="amount" invisible="1"/>
|
||||
<field name="unit_amount" widget="timesheet_uom" decoration-danger="unit_amount > 24"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_view_form_user" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.tree.with.user</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='date']" position="before">
|
||||
<field name="employee_id" required="1" context="{'active_test': True}"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Base search view, contains the fields and filters common to timesheet and timesheet report -->
|
||||
<record id="hr_timesheet_line_search_base" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.search</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Timesheet">
|
||||
<field name="date"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="project_id"/>
|
||||
<field name="task_id"/>
|
||||
<field name="parent_task_id" groups="project.group_subtask_project"/>
|
||||
<field name="name"/>
|
||||
<field name="department_id"/>
|
||||
<field name="manager_id"/>
|
||||
<filter name="mine" string="My Timesheets" domain="[('user_id', '=', uid)]"/>
|
||||
<separator/>
|
||||
<filter name="month" string="Date" date="date"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Project" name="groupby_project" domain="[]" context="{'group_by': 'project_id'}"/>
|
||||
<filter string="Parent Task" name="groupby_parent_task" domain="[]" context="{'group_by': 'parent_task_id'}" groups="project.group_subtask_project"/>
|
||||
<filter string="Task" name="groupby_task" domain="[]" context="{'group_by': 'task_id'}"/>
|
||||
<filter string="Date" name="groupby_date" domain="[]" context="{'group_by': 'date'}" help="Timesheet by Date"/>
|
||||
<filter string="Department" name="groupby_department" domain="[]" context="{'group_by': 'department_id'}"/>
|
||||
<filter string="Manager" name="groupby_manager" domain="[]" context="{'group_by': 'manager_id'}"/>
|
||||
<filter string="Employee" name="groupby_employee" domain="[]" context="{'group_by': 'employee_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_timesheet_line_my_timesheet_search" model="ir.ui.view">
|
||||
<field name="name">view.search.my.timesheet.menu</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet_line_search_base"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="employee_id" position="replace"/>
|
||||
<field name="department_id" position="replace"/>
|
||||
<field name="manager_id" position="replace"/>
|
||||
<filter name="mine" position="replace"/>
|
||||
<filter name="groupby_department" position="replace"/>
|
||||
<filter name="groupby_manager" position="replace"/>
|
||||
<filter name="groupby_employee" position="replace"/>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_timesheet_line_search" model="ir.ui.view">
|
||||
<field name="name">view.search.my.timesheet.menu</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet_line_search_base"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<search position="inside"/>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_kanban_account_analytic_line" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.kanban</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile" sample="1">
|
||||
<field name="date"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="name"/>
|
||||
<field name="project_id"/>
|
||||
<field name="task_id" context="{'default_project_id': project_id}" domain="[('project_id', '=', project_id)]"/>
|
||||
<field name="unit_amount" widget="timesheet_uom"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click o_kanban_record_has_image_fill px-0 pb-0">
|
||||
<div class="oe_kanban_details d-flex flex-column">
|
||||
<div class="o_kanban_record_top px-2 h-50">
|
||||
<img t-att-src="kanban_image('hr.employee', 'avatar_128', record.employee_id.raw_value)" t-att-title="record.employee_id.value" t-att-alt="record.employee_id.value" class="o_image_40_cover float-start"/>
|
||||
<div class="o_kanban_record_headings ps-4 pe-2">
|
||||
<div class="text-truncate">
|
||||
<strong class="o_kanban_record_title">
|
||||
<span t-esc="record.project_id.value" t-att-title="record.project_id.value"/>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="text-truncate">
|
||||
<i><span t-esc="record.task_id.value" t-att-title="record.task_id.value"/></i>
|
||||
</div>
|
||||
<div class="text-truncate">
|
||||
<span t-esc="record.name.value" t-att-title="record.name.value"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_kanban_record_bottom d-block mx-2">
|
||||
<hr class="mt4 mb4"/>
|
||||
<span>
|
||||
<i class="fa fa-calendar me-1" role="img" aria-label="Date" title="Date"></i>
|
||||
<t t-esc="record.date.value"/>
|
||||
</span>
|
||||
<span class="float-end" name="duration">
|
||||
<strong>Duration: </strong><field name="unit_amount" widget="timesheet_uom" decoration-danger="unit_amount > 24"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Menus and Actions
|
||||
-->
|
||||
<record id="act_hr_timesheet_line" model="ir.actions.act_window">
|
||||
<field name="name">My Timesheets</field>
|
||||
<field name="res_model">account.analytic.line</field>
|
||||
<field name="view_mode">tree,form,kanban,pivot,graph</field>
|
||||
<field name="domain">[('project_id', '!=', False), ('user_id', '=', uid)]</field>
|
||||
<field name="context">{
|
||||
"search_default_week":1,
|
||||
"is_timesheet": 1,
|
||||
}</field>
|
||||
<field name="search_view_id" ref="hr_timesheet_line_my_timesheet_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No activities found. Let's start a new one!
|
||||
</p>
|
||||
<p>
|
||||
Track your working hours by projects every day and invoice this time to your customers.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="act_hr_timesheet_line_view_tree" model="ir.actions.act_window.view">
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="sequence" eval="4"/>
|
||||
<field name="view_id" ref="hr_timesheet_line_tree"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_line"/>
|
||||
</record>
|
||||
|
||||
<record id="act_hr_timesheet_line_view_form" model="ir.actions.act_window.view">
|
||||
<field name="view_mode">form</field>
|
||||
<field name="sequence" eval="5"/>
|
||||
<field name="view_id" ref="hr_timesheet_line_form"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_line"/>
|
||||
</record>
|
||||
|
||||
<record id="act_hr_timesheet_line_view_kanban" model="ir.actions.act_window.view">
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="sequence">6</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_kanban_account_analytic_line"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_line"/>
|
||||
</record>
|
||||
|
||||
<record id="act_hr_timesheet_line_view_pivot" model="ir.actions.act_window.view">
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="sequence">7</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_my_timesheet_line_pivot"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_line"/>
|
||||
</record>
|
||||
|
||||
<record id="act_hr_timesheet_line_view_graph" model="ir.actions.act_window.view">
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="sequence">8</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_hr_timesheet_line_graph_my"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_line"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="timesheet_menu_activity_mine"
|
||||
name="My Timesheets"
|
||||
groups="group_hr_timesheet_approver"
|
||||
parent="menu_hr_time_tracking"
|
||||
action="act_hr_timesheet_line"/>
|
||||
|
||||
<menuitem id="timesheet_menu_activity_user"
|
||||
name="My Timesheets"
|
||||
groups="group_hr_timesheet_user"
|
||||
parent="timesheet_menu_root"
|
||||
action="act_hr_timesheet_line"/>
|
||||
|
||||
<record id="timesheet_action_task" model="ir.actions.act_window">
|
||||
<field name="name">Task's Timesheets</field>
|
||||
<field name="res_model">account.analytic.line</field>
|
||||
<field name="context">{
|
||||
'is_timesheet': 1,
|
||||
}</field>
|
||||
<field name="domain">[('task_id', 'in', active_ids)]</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="timesheet_view_tree_user"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_project" model="ir.actions.act_window">
|
||||
<field name="name">Project's Timesheets</field>
|
||||
<field name="res_model">account.analytic.line</field>
|
||||
<field name="context">{
|
||||
'is_timesheet': 1,
|
||||
}</field>
|
||||
<field name="domain">[('project_id', 'in', active_ids)]</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="timesheet_view_tree_user"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_all" model="ir.actions.act_window">
|
||||
<field name="name">All Timesheets</field>
|
||||
<field name="res_model">account.analytic.line</field>
|
||||
<field name="view_mode">tree,form,kanban,pivot,graph</field>
|
||||
<field name="search_view_id" ref="hr_timesheet_line_search"/>
|
||||
<field name="domain">[('project_id', '!=', False)]</field>
|
||||
<field name="context">{
|
||||
'search_default_week':1,
|
||||
'is_timesheet': 1,
|
||||
}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No activities found. Let's start a new one!
|
||||
</p>
|
||||
<p>
|
||||
Track your working hours by projects every day and invoice this time to your customers.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_all_tree" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="4"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="timesheet_view_tree_user"/>
|
||||
<field name="act_window_id" ref="timesheet_action_all"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_all_form" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="5"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="timesheet_view_form_user"/>
|
||||
<field name="act_window_id" ref="timesheet_action_all"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_all_kanban" model="ir.actions.act_window.view">
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="sequence">6</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_kanban_account_analytic_line"/>
|
||||
<field name="act_window_id" ref="timesheet_action_all"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_all_pivot" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="7"/>
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="view_id" ref="view_hr_timesheet_line_pivot"/>
|
||||
<field name="act_window_id" ref="timesheet_action_all"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_all_graph" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="8"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="view_hr_timesheet_line_graph_all"/>
|
||||
<field name="act_window_id" ref="timesheet_action_all"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="timesheet_menu_activity_all"
|
||||
name="All Timesheets"
|
||||
parent="menu_hr_time_tracking"
|
||||
action="timesheet_action_all"
|
||||
groups="hr_timesheet.group_hr_timesheet_approver"/>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,394 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="act_hr_timesheet_line_by_project" model="ir.actions.act_window">
|
||||
<field name="name">Timesheets</field>
|
||||
<field name="res_model">account.analytic.line</field>
|
||||
<field name="view_mode">tree,kanban,pivot,graph,form</field>
|
||||
<field name="view_id" ref="timesheet_view_tree_user"/>
|
||||
<field name="domain">[('project_id', '=', active_id)]</field>
|
||||
<field name="context">{
|
||||
"default_project_id": active_id,
|
||||
"is_timesheet": 1,
|
||||
}</field>
|
||||
<field name="search_view_id" ref="hr_timesheet_line_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Record a new activity
|
||||
</p><p>
|
||||
Track your working hours by projects every day and invoice this time to your customers.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="act_hr_timesheet_line_by_project_view_tree" model="ir.actions.act_window.view">
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="view_id" ref="timesheet_view_tree_user"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_line_by_project"/>
|
||||
</record>
|
||||
|
||||
<record id="act_hr_timesheet_line_by_project_view_kanban" model="ir.actions.act_window.view">
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="view_id" ref="view_kanban_account_analytic_line"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_line_by_project"/>
|
||||
</record>
|
||||
|
||||
<record id="act_hr_timesheet_line_by_project_view_pivot" model="ir.actions.act_window.view">
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="sequence" eval="3"/>
|
||||
<field name="view_id" ref="view_hr_timesheet_line_pivot"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_line_by_project"/>
|
||||
</record>
|
||||
|
||||
<record id="act_hr_timesheet_line_by_project_view_graph" model="ir.actions.act_window.view">
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="sequence" eval="4"/>
|
||||
<field name="view_id" ref="view_hr_timesheet_line_graph_all"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_line_by_project"/>
|
||||
</record>
|
||||
|
||||
<record id="act_hr_timesheet_line_by_project_view_form" model="ir.actions.act_window.view">
|
||||
<field name="view_mode">form</field>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view_id" ref="hr_timesheet_line_form"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_line_by_project"/>
|
||||
</record>
|
||||
|
||||
<record id="project_project_view_form_simplified_inherit_timesheet" model="ir.ui.view">
|
||||
<field name="name">project.project.view.form.simplified.inherit.timesheet</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.project_project_view_form_simplified"/>
|
||||
<field name="priority">24</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('o_settings_container')]" position="inside">
|
||||
<setting string="Timesheets" help="Log time on tasks">
|
||||
<field name="allow_timesheets"/>
|
||||
</setting>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_invoice_form" model="ir.ui.view">
|
||||
<field name="name">Inherit project form : Invoicing Data</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.edit_project"/>
|
||||
<field name="priority">24</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='dates']" position="after">
|
||||
<field name="allocated_hours" widget="timesheet_uom_no_toggle" attrs="{'invisible': [('allow_timesheets', '=', False)]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='group_time_managment']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='group_time_managment']" position="inside">
|
||||
<setting id="timesheet_settings" string="Timesheets" help="Log time on tasks">
|
||||
<field name="allow_timesheets"/>
|
||||
</setting>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_task_form2_inherited">
|
||||
<field name="name">project.task.form.inherited</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='child_ids']/tree/field[@name='company_id']" position="after">
|
||||
<field name="planned_hours" widget="timesheet_uom_no_toggle" sum="Initially Planned Hours" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="effective_hours" widget="timesheet_uom" sum="Hours Spent" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="subtask_effective_hours" widget="timesheet_uom" sum="Sub-tasks Hours Spent" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="total_hours_spent" widget="timesheet_uom" sum="Total Hours" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="remaining_hours" widget="timesheet_uom" sum="Remaining Hours" optional="hide" decoration-danger="progress >= 100" decoration-warning="progress >= 80 and progress < 100" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="progress" widget="progressbar" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
</xpath>
|
||||
<xpath expr="//label[@for='date_deadline']" position="before" groups="hr_timesheet.group_hr_timesheet_user">
|
||||
<field name="encode_uom_in_days" invisible="1"/>
|
||||
<field name="subtask_count" invisible="1"/>
|
||||
<label for="planned_hours" string="Allocated Hours" attrs="{'invisible': ['|', ('allow_timesheets', '=', False), ('encode_uom_in_days', '=', True)]}"/>
|
||||
<label for="planned_hours" string="Allocated Days" attrs="{'invisible': ['|', ('allow_timesheets', '=', False), ('encode_uom_in_days', '=', False)]}"/>
|
||||
<div class="text-nowrap" attrs="{'invisible': [('allow_timesheets', '=', False)]}">
|
||||
<field name="planned_hours" class="oe_inline" widget="float_time"/>
|
||||
<span attrs="{'invisible': ['|', ('allow_subtasks', '=', False), ('subtask_count', '=', 0)]}">
|
||||
(incl. <field name="subtask_planned_hours" nolabel="1" groups="project.group_subtask_project" widget="timesheet_uom_no_toggle" class="oe_inline"/> on
|
||||
<span class="fw-bold text-dark"> Sub-tasks</span>)
|
||||
</span>
|
||||
<span attrs="{'invisible': [('project_id', '=', False)]}">(<field name="progress" class="oe_inline" nolabel="1" widget="integer"/> %)</span>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//notebook/page[@name='description_page']" position="after">
|
||||
<field name="allow_timesheets" invisible="1"/>
|
||||
<field name="allow_subtasks" invisible="1"/>
|
||||
<t groups="hr_timesheet.group_hr_timesheet_user">
|
||||
<field name="analytic_account_active" invisible="1"/>
|
||||
</t>
|
||||
<page string="Timesheets" id="timesheets_tab" attrs="{'invisible': [('allow_timesheets', '=', False)]}" groups="hr_timesheet.group_hr_timesheet_user">
|
||||
<group name="timesheet_error" attrs="{'invisible': [('analytic_account_active', '!=', False)]}">
|
||||
<div class="alert alert-warning" role="alert" colspan="2">
|
||||
You cannot log timesheets on this project since it is linked to an inactive analytic account. Please change this account, or reactivate the current one to timesheet on the project.
|
||||
</div>
|
||||
</group>
|
||||
<field name="timesheet_ids" mode="tree,kanban" attrs="{'invisible': [('analytic_account_active', '=', False)]}" context="{'default_project_id': project_id, 'default_name':''}">
|
||||
<tree editable="bottom" string="Timesheet Activities" default_order="date">
|
||||
<field name="date"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
<field name="employee_id" required="1" widget="many2one_avatar_employee" context="{'active_test': True}"/>
|
||||
<field name="name" required="0"/>
|
||||
<field name="unit_amount" widget="timesheet_uom" decoration-danger="unit_amount > 24 or unit_amount < 0"/>
|
||||
<field name="project_id" invisible="1"/>
|
||||
<field name="task_id" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
</tree>
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="date"/>
|
||||
<field name="user_id"/>
|
||||
<field name="name"/>
|
||||
<field name="unit_amount" decoration-danger="unit_amount > 24"/>
|
||||
<field name="project_id"/>
|
||||
<field name="task_id" invisible="1"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<field name="employee_id" widget="many2one_avatar_employee" context="{'active_test': True}"/>
|
||||
<strong><span><t t-esc="record.employee_id.value"/></span></strong>
|
||||
</div>
|
||||
<div class="col-6 float-end text-end">
|
||||
<strong><t t-esc="record.date.value"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 text-muted">
|
||||
<span><t t-esc="record.name.value"/></span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<span class="float-end text-end">
|
||||
<field name="unit_amount" widget="float_time"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
<form string="Timesheet Activities">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="date"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
<field name="employee_id" required="1" widget="many2one_avatar_employee" context="{'active_test': True}"/>
|
||||
<field name="name" required="0"/>
|
||||
<field name="unit_amount" string="Duration" widget="float_time" decoration-danger="unit_amount > 24"/>
|
||||
<field name="project_id" invisible="1"/>
|
||||
<field name="task_id" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
<group attrs="{'invisible': [('analytic_account_active', '=', False)]}">
|
||||
<group class="oe_subtotal_footer oe_right" name="project_hours">
|
||||
<span class="o_td_label float-start">
|
||||
<label class="fw-bold" for="effective_hours" string="Hours Spent" attrs="{'invisible': [('encode_uom_in_days', '=', True)]}"/>
|
||||
<label class="fw-bold" for="effective_hours" string="Days Spent" attrs="{'invisible': [('encode_uom_in_days', '=', False)]}"/>
|
||||
</span>
|
||||
<field name="effective_hours" widget="timesheet_uom" nolabel="1"/>
|
||||
<button name="action_view_subtask_timesheet" type="object" class="ps-0 border-0 oe_inline oe_link mb-2 o_td_label float-start" attrs="{'invisible' : ['|', ('allow_subtasks', '=', False), ('subtask_effective_hours', '=', 0.0)]}">
|
||||
<span class="text-nowrap" attrs="{'invisible' : [('encode_uom_in_days', '=', True)]}">Hours Spent on Sub-tasks:</span>
|
||||
<span class="text-nowrap" attrs="{'invisible' : [('encode_uom_in_days', '=', False)]}">Days Spent on Sub-tasks:</span>
|
||||
</button>
|
||||
<field name="subtask_effective_hours" class="mt-2" widget="timesheet_uom"
|
||||
attrs="{'invisible' : ['|', ('allow_subtasks', '=', False), ('subtask_effective_hours', '=', 0.0)]}" nolabel="1"/>
|
||||
<span attrs="{'invisible': ['|', ('allow_subtasks', '=', False), ('subtask_effective_hours', '=', 0.0)]}" class="o_td_label float-start">
|
||||
<label class="fw-bold" for="total_hours_spent" string="Total Hours"
|
||||
attrs="{'invisible': ['|', '|', ('allow_subtasks', '=', False), ('subtask_effective_hours', '=', 0.0), ('encode_uom_in_days', '=', True)]}"/>
|
||||
<label class="fw-bold" for="total_hours_spent" string="Total Days"
|
||||
attrs="{'invisible': ['|', '|', ('allow_subtasks', '=', False), ('subtask_effective_hours', '=', 0.0), ('encode_uom_in_days', '=', False)]}"/>
|
||||
</span>
|
||||
<field name="total_hours_spent" widget="timesheet_uom" class="oe_subtotal_footer_separator" nolabel="1"
|
||||
attrs="{'invisible' : ['|', ('allow_subtasks', '=', False), ('subtask_effective_hours', '=', 0.0)]}" />
|
||||
<span class="o_td_label float-start">
|
||||
<label class="fw-bold" for="remaining_hours" string="Remaining Hours"
|
||||
attrs="{'invisible': ['|', '|', ('planned_hours', '=', 0.0), ('encode_uom_in_days', '=', True), ('remaining_hours', '<', 0)]}"/>
|
||||
<label class="fw-bold" for="remaining_hours" string="Remaining Days"
|
||||
attrs="{'invisible': ['|', '|', ('planned_hours', '=', 0.0), ('encode_uom_in_days', '=', False), ('remaining_hours', '<', 0)]}"/>
|
||||
<label class="fw-bold text-danger" for="remaining_hours" string="Remaining Hours"
|
||||
attrs="{'invisible': ['|', '|', ('planned_hours', '=', 0.0), ('encode_uom_in_days', '=', True), ('remaining_hours', '>=', 0)]}"/>
|
||||
<label class="fw-bold text-danger" for="remaining_hours" string="Remaining Days"
|
||||
attrs="{'invisible': ['|', '|', ('planned_hours', '=', 0.0), ('encode_uom_in_days', '=', False), ('remaining_hours', '>=', 0)]}"/>
|
||||
</span>
|
||||
<field name="remaining_hours" widget="timesheet_uom" class="oe_subtotal_footer_separator"
|
||||
attrs="{'invisible' : [('planned_hours', '=', 0.0)]}" nolabel="1" decoration-danger="remaining_hours < 0"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='depend_on_ids']/tree//field[@name='company_id']" position="after">
|
||||
<field name="allow_subtasks" invisible="1" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="progress" invisible="1" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="planned_hours" widget="timesheet_uom_no_toggle" sum="Initially Planned Hours" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="effective_hours" widget="timesheet_uom" sum="Effective Hours" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="subtask_effective_hours" widget="timesheet_uom" attrs="{'invisible' : [('allow_subtasks', '=', False)]}" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="total_hours_spent" widget="timesheet_uom" attrs="{'invisible' : [('allow_subtasks', '=', False)]}" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="remaining_hours" widget="timesheet_uom" sum="Remaining Hours" optional="hide" decoration-danger="progress >= 100" decoration-warning="progress >= 80 and progress < 100" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<field name="progress" widget="progressbar" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_project_view_tree_inherit_sale_project" model="ir.ui.view">
|
||||
<field name="name">project.project.tree.inherit.sale.timesheet</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.view_project"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='date']" position="after">
|
||||
<field name="allocated_hours" widget="timesheet_uom_no_toggle" optional="hide" attrs="{'invisible' : [('allocated_hours', '=', 0)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_task_tree2_inherited" model="ir.ui.view">
|
||||
<field name="name">project.task.tree.inherited</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.project_task_view_tree_main_base" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="date_deadline" position="before">
|
||||
<field name="allow_subtasks" invisible="1"/>
|
||||
<field name="progress" invisible="1"/>
|
||||
<field name="planned_hours" widget="timesheet_uom_no_toggle" sum="Initially Planned Hours" optional="hide"/>
|
||||
<field name="effective_hours" widget="timesheet_uom" sum="Effective Hours" optional="show"/>
|
||||
<field name="subtask_effective_hours" widget="timesheet_uom" attrs="{'invisible' : [('allow_subtasks', '=', False)]}" optional="hide"/>
|
||||
<field name="total_hours_spent" widget="timesheet_uom" attrs="{'invisible' : [('allow_subtasks', '=', False)]}" optional="hide"/>
|
||||
<field name="remaining_hours" widget="timesheet_uom" sum="Remaining Hours" optional="hide" decoration-danger="progress >= 100" decoration-warning="progress >= 80 and progress < 100"/>
|
||||
<field name="progress" widget="progressbar" optional="show" groups="hr_timesheet.group_hr_timesheet_user" attrs="{'invisible' : [('planned_hours', '=', 0)]}"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_project_kanban_inherited" model="ir.ui.view">
|
||||
<field name="name">project.project.timesheet.kanban.inherited</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.view_project_kanban"/>
|
||||
<field name="priority">24</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_id" position="after">
|
||||
<field name="allow_timesheets" invisible="1"/>
|
||||
<field name="timesheet_count" invisible="1"/>
|
||||
<field name="remaining_hours" invisible="1"/>
|
||||
<field name="encode_uom_in_days" invisible="1"/>
|
||||
<field name="allocated_hours" invisible="1"/>
|
||||
</field>
|
||||
<xpath expr="//div[hasclass('o_kanban_manage_view')]" position="inside">
|
||||
<div role="menuitem" t-if="record.allow_timesheets.raw_value and record.timesheet_count and record.timesheet_count.raw_value" groups="hr_timesheet.group_hr_timesheet_user">
|
||||
<a name="action_project_timesheets" type="object">Timesheets</a>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('o_project_kanban_boxes')]" position="after">
|
||||
<t t-set="badgeColor" t-value="'border-success'"/>
|
||||
<t t-set="badgeColor" t-value="'border-warning'" t-if="record.remaining_hours.raw_value / record.allocated_hours.raw_value < 0.2 and record.remaining_hours.raw_value / record.allocated_hours.raw_value > 0"/>
|
||||
<t t-set="badgeColor" t-value="'border-danger'" t-elif="record.remaining_hours.raw_value < 0"/>
|
||||
<t t-set="title" t-value="'Remaining days'" t-if="record.encode_uom_in_days.raw_value"/>
|
||||
<t t-set="title" t-value="'Remaining hours'" t-else=""/>
|
||||
<div t-if="record.allow_timesheets.raw_value and record.allocated_hours.raw_value > 0"
|
||||
t-attf-class="oe_kanban_align badge border {{ badgeColor }}" t-att-title="title">
|
||||
<field name="remaining_hours" widget="timesheet_uom"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_task_kanban_inherited_progress" model="ir.ui.view">
|
||||
<field name="name">project.task.timesheet.kanban.inherited.progress</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<templates position="before">
|
||||
<field name="progress" />
|
||||
<field name="remaining_hours" />
|
||||
<field name="planned_hours" />
|
||||
<field name="allow_timesheets"/>
|
||||
<field name="encode_uom_in_days" invisible="1"/>
|
||||
</templates>
|
||||
<div class="oe_kanban_bottom_left" position="inside">
|
||||
<t name="planned_hours" t-if="record.planned_hours.raw_value > 0 and record.allow_timesheets.raw_value">
|
||||
<t t-set="badge" t-value=""/>
|
||||
<t t-set="badge" t-value="'border-warning'" t-if="record.progress.raw_value >= 80 and record.progress.raw_value <= 100"/>
|
||||
<t t-set="badge" t-value="'border-danger'" t-if="record.remaining_hours.raw_value < 0"/>
|
||||
<t t-set="title" t-value="'Remaining days'" t-if="record.encode_uom_in_days.raw_value"/>
|
||||
<t t-set="title" t-value="'Remaining hours'" t-else=""/>
|
||||
<div t-attf-class="oe_kanban_align badge {{ badge }}" t-att-title="title">
|
||||
<field name="remaining_hours" widget="timesheet_uom" />
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_task_view_search" model="ir.ui.view">
|
||||
<field name="name">project.task.view.search.inherit.sale.timesheet.enterprise</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_search_form_project_fsm_base"/>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='blocking']/following-sibling::separator[1]" position="after">
|
||||
<filter string="Timesheets 80%" name="timesheet_80" domain="[('remaining_hours_percentage', '>', 0.0), ('remaining_hours_percentage', '<=', 0.2)]"/>
|
||||
<filter string="Timesheets >100%" name="timesheet_exceeded" domain="[('overtime', '>', 0)]"/>
|
||||
<separator/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_project_project_filter_inherit_timesheet" model="ir.ui.view">
|
||||
<field name="name">project.project.view.inherit.timesheet</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.view_project_project_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<filter name="late_milestones" position="before">
|
||||
<filter string="Timesheets Exceeded" name="projects_in_overtime" domain="[('is_project_overtime', '=', True)]"/>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_task_view_graph" model="ir.ui.view">
|
||||
<field name="name">project.task.view.graph.inherited</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_project_task_graph"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//graph" position="attributes">
|
||||
<attribute name="js_class">hr_timesheet_graphview</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='stage_id']" position='after'>
|
||||
<field name="planned_hours" widget="timesheet_uom"/>
|
||||
<field name="remaining_hours" widget="timesheet_uom"/>
|
||||
<field name="effective_hours" widget="timesheet_uom"/>
|
||||
<field name="total_hours_spent" widget="timesheet_uom"/>
|
||||
<field name="overtime" widget="timesheet_uom"/>
|
||||
<field name="subtask_effective_hours" widget="timesheet_uom"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_task_view_pivot" model="ir.ui.view">
|
||||
<field name="name">project.task.view.pivot.inherited</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_project_task_pivot"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='stage_id']" position='after'>
|
||||
<field name="planned_hours" widget="timesheet_uom"/>
|
||||
<field name="remaining_hours" widget="timesheet_uom"/>
|
||||
<field name="effective_hours" widget="timesheet_uom"/>
|
||||
<field name="total_hours_spent" widget="timesheet_uom"/>
|
||||
<field name="overtime" widget="timesheet_uom"/>
|
||||
<field name="subtask_effective_hours" widget="timesheet_uom"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project.open_view_project_all" model="ir.actions.act_window">
|
||||
<field name="domain">[('is_internal_project', '=', False)]</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user