[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:
committed by
Thibault Delavallée
parent
69631078ba
commit
30384249e1
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user