[ADD] project_timesheet_holidays: generate timesheet when on leaves

This commit proposes to allow to automatically generate timesheet lines
when an employee is on leaves. Project and task used for timesheeting
is globally configured on the company. Holidays type choose whether to
generate timesheets or not and take the company default configuration.

When an HR officer validates a leave request, analytic lines will be
generated for each day off. The unit amount will be based on the working
calendar of the employee taking the leave. Generated timesheet lines are
attached to the project and task configured on the holidays type.

Configuring timesheet lines generation at holiday type level allows to
tune the use of leave requests. You may have for example "home working"
holiday type that should not generate timesheets while "legal leaves"
should.
This commit is contained in:
Jérome Maes
2017-05-03 10:15:27 +02:00
committed by Thibault Delavallée
parent 69631078ba
commit 30384249e1
13 changed files with 271 additions and 16 deletions
+10 -4
View File
@@ -471,10 +471,7 @@ class Holidays(models.Model):
else:
holiday.write({'first_approver_id': current_employee.id})
if holiday.holiday_type == 'employee' and holiday.type == 'remove':
meeting_values = holiday._prepare_holidays_meeting_values()
meeting = self.env['calendar.event'].with_context(no_mail_to_attendees=True).create(meeting_values)
holiday._create_resource_leave()
holiday.write({'meeting_id': meeting.id})
holiday._validate_leave_request()
elif holiday.holiday_type == 'category':
leaves = self.env['hr.holidays']
for employee in holiday.category_id.employee_ids:
@@ -486,6 +483,15 @@ class Holidays(models.Model):
leaves.action_validate()
return True
def _validate_leave_request(self):
""" Validate leave requests (holiday_type='employee' and holiday.type='remove')
by creating a calendar event and a resource leaves. """
for holiday in self.filtered(lambda request: request.type == 'remove' and request.holiday_type == 'employee'):
meeting_values = holiday._prepare_holidays_meeting_values()
meeting = self.env['calendar.event'].with_context(no_mail_to_attendees=True).create(meeting_values)
holiday.write({'meeting_id': meeting.id})
holiday._create_resource_leave()
@api.multi
def _prepare_holidays_meeting_values(self):
self.ensure_one()
+19 -12
View File
@@ -564,19 +564,26 @@
<field name="model">hr.holidays.status</field>
<field name="arch" type="xml">
<form string="Leave Type">
<group>
<group name="selection">
<field name="name"/>
<field name="double_validation"/>
<field name="limit"/>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" type="object" name="toggle_active" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<group>
<group name="description" string="Description">
<field name="name"/>
<field name="double_validation"/>
<field name="limit"/>
</group>
<group name="options" string="Options">
<field name="categ_id"/>
<field name="color_name"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<group name="second">
<field name="categ_id"/>
<field name="color_name"/>
<field name="active"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
@@ -14,3 +14,4 @@ class HrTimesheetConfiguration(models.TransientModel):
module_hr_expense = fields.Boolean("Expenses")
module_hr_holidays = fields.Boolean("Leave Management")
module_hr_timesheet_attendance = fields.Boolean("Attendances")
module_project_timesheet_holidays = fields.Boolean("Leaves")
@@ -13,6 +13,7 @@
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
</header>
<sheet>
<field name="company_id" invisible="1"/>
<h2>Timesheet Apps</h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box">
@@ -65,6 +66,25 @@
</div>
</div>
</div>
<h2>Leaves</h2>
<div class="row mt16 o_settings_container" name="timesheet_control">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_project_timesheet_holidays"/>
</div>
<div class="o_setting_right_pane">
<label for="module_project_timesheet_holidays"/>
<div class="text-muted">
Validated leaves create timesheets automatically.
</div>
<div class="content-group">
<div class="text-warning mt16" id="module_project_timesheet_holidays" attrs="{'invisible': [('module_project_timesheet_holidays','=',False)]}">
Save this page and come back here to set up the feature.
</div>
</div>
</div>
</div>
</div>
<h2>Recommended Apps</h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box">
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Timesheet when on Leaves',
'version': '1.0',
'category': 'Human Resources',
'summary': 'Schedule timesheet when on leaves',
'description': """
Bridge module to integrate holidays in timesheet
================================================
This module allows to automatically log timesheets when employees are
on leaves. Project and task can be configured company-wide.
""",
'depends': ['hr_timesheet', 'hr_holidays'],
'data': [
'views/res_config_views.xml',
'views/hr_holidays_views.xml',
],
'demo': [],
'installable': True,
'auto_install': True,
}
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_analytic
from . import hr_holidays
from . import res_company
from . import res_config
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class AccountAnalyticLine(models.Model):
_inherit = 'account.analytic.line'
holiday_id = fields.Many2one("hr.holidays", string='Leave Request')
@api.multi
def unlink(self):
if any(line.holiday_id for line in self):
raise UserError(_('You cannot delete timesheet lines attached to a leaves. Please cancel the leaves instead.'))
return super(AccountAnalyticLine, self).unlink()
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class HolidaysType(models.Model):
_inherit = "hr.holidays.status"
timesheet_generate = fields.Boolean('Generate Timesheet', help="If checked, when validating a leave, timesheet will be generated in the Vacation Project of the company.")
timesheet_project_id = fields.Many2one('project.project', string="Internal Project", help="The project will contain the timesheet generated when a leave is validated.")
timesheet_task_id = fields.Many2one('project.task', string="Internal Task for timesheet", domain="[('project_id', '=', timesheet_project_id)]")
@api.onchange('timesheet_generate')
def _onchange_timesheet_generate(self):
if self.timesheet_generate:
company = self.company_id if self.company_id else self.env.user.company_id
self.timesheet_project_id = company.leave_timesheet_project_id
self.timesheet_task_id = company.leave_timesheet_task_id
else:
self.timesheet_project_id = False
self.timesheet_task_id = False
class Holidays(models.Model):
_inherit = "hr.holidays"
timesheet_ids = fields.One2many('account.analytic.line', 'holiday_id', string="Analytic Lines")
def _validate_leave_request(self):
""" Timesheet will be generated on leave validation only if a timesheet_project_id and a
timesheet_task_id are set on the corresponding leave type. The generated timesheet will
be attached to this project/task.
"""
# create the timesheet on the vacation project
for holiday in self.filtered(
lambda request: request.type == 'remove' and
request.holiday_type == 'employee' and
request.holiday_status_id.timesheet_project_id and
request.holiday_status_id.timesheet_task_id):
holiday_project = holiday.holiday_status_id.timesheet_project_id
holiday_task = holiday.holiday_status_id.timesheet_task_id
work_hours_data = [item for item in holiday.employee_id.iter_work_hours_count(fields.Datetime.from_string(holiday.date_from), fields.Datetime.from_string(holiday.date_to))]
for index, (day_date, work_hours_count) in enumerate(work_hours_data):
self.env['account.analytic.line'].create({
'name': "%s (%s/%s)" % (holiday.name, index + 1, len(work_hours_data)),
'project_id': holiday_project.id,
'task_id': holiday_task.id,
'account_id': holiday_project.analytic_account_id.id,
'unit_amount': work_hours_count,
'user_id': holiday.employee_id.user_id.id,
'date': fields.Date.to_string(day_date),
'holiday_id': holiday.id,
})
return super(Holidays, self)._validate_leave_request()
@api.multi
def action_refuse(self):
""" Remove the timesheets linked to the refused holidays """
result = super(Holidays, self).action_refuse()
self.mapped('timesheet_ids').unlink()
return result
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
class Company(models.Model):
_inherit = 'res.company'
leave_timesheet_project_id = fields.Many2one(
'project.project', string="Internal Project",
help="Default project value for timesheet generated from leave type.")
leave_timesheet_task_id = fields.Many2one(
'project.task', string="Leave Task",
domain="[('project_id', '=', leave_timesheet_project_id)]")
def _init_column(self, name):
super(Company, self)._init_column(name)
if name == 'leave_timesheet_project_id':
self.search([('leave_timesheet_project_id', '=', False)])._create_leave_project_task()
@api.model
def create(self, values):
company = super(Company, self).create(values)
company._create_leave_project_task()
return company
def _create_leave_project_task(self):
for company in self:
if not company.leave_timesheet_project_id:
project = self.env['project.project'].create({
'name': _('Internal Project'),
'allow_timesheets': True,
'active': False,
'company_id': company.id,
})
company.write({
'leave_timesheet_project_id': project.id,
})
if not company.leave_timesheet_task_id:
task = self.env['project.task'].create({
'name': _('Leaves'),
'project_id': company.leave_timesheet_project_id.id,
'active': False,
})
company.write({
'leave_timesheet_task_id': task.id,
})
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class TimesheetConfig(models.TransientModel):
_inherit = 'hr.timesheet.config.settings'
leave_timesheet_project_id = fields.Many2one(related='company_id.leave_timesheet_project_id', string="Internal Project")
leave_timesheet_task_id = fields.Many2one(related='company_id.leave_timesheet_task_id', string="Leave Task")
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_holiday_status_view_form_inherit" model="ir.ui.view">
<field name="name">hr.holidays.status.form</field>
<field name="model">hr.holidays.status</field>
<field name="inherit_id" ref="hr_holidays.edit_holiday_status_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='description']" position="after">
<group name="timesheet" string="Timesheet">
<field name="timesheet_generate"/>
<field name="timesheet_project_id" attrs="{'invisible': [('timesheet_generate', '!=', True)], 'required': [('timesheet_generate', '=', True)]}" context="{'active_test': False}"/>
<field name="timesheet_task_id" attrs="{'invisible': [('timesheet_generate', '!=', True)], 'required': [('timesheet_generate', '=', True)]}" context="{'active_test': False}"/>
</group>
</xpath>
</field>
</record>
</odoo>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="timesheet_config_settings_view_form_inherit" model="ir.ui.view">
<field name="name">hr.timesheet.config.settings.view.form</field>
<field name="model">hr.timesheet.config.settings</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='module_project_timesheet_holidays']" position="replace">
<div attrs="{'invisible': [('module_project_timesheet_holidays','=',False)]}">
<div class="row mt16">
<div>
<label string="Project" for="leave_timesheet_project_id" class="col-xs-3 col-md-3 o_light_label"/>
<field name="leave_timesheet_project_id" context="{'active_test': False}" class="oe_inline"/>
</div>
<div>
<label string="Task" for="leave_timesheet_task_id" class="col-xs-3 col-md-3 o_light_label"/>
<field name="leave_timesheet_task_id" context="{'active_test': False, 'default_project_id': leave_timesheet_project_id}" class="oe_inline"/>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>