[ADD] hr_holidays_attendance: convert overtime to holidays
This module allows an employee to create leave requests or allocation
requests from their overtime.
Depending on the configuration of the leave type, the request can be
made from:
- "My Profile" for allocation which allows employee requests;
- Time Off dashboard for leave type with no allocations;
- The employee profile for the time off manager.
TaskID: 1904850
[IMP] hr_holidays{,_attendance}: leave allocation multi create
Change leave allocation create function to a multi create one.
Task ID: 1904850
This commit is contained in:
committed by
William Braeckman
parent
9e6d2da504
commit
2961513141
@@ -66,6 +66,7 @@ class HolidaysAllocation(models.Model):
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee', compute='_compute_from_holiday_type', store=True, string='Employee', index=True, readonly=False, ondelete="restrict", tracking=True,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
employee_company_id = fields.Many2one(related='employee_id.company_id', readonly=True)
|
||||
active_employee = fields.Boolean('Active Employee', related='employee_id.active', readonly=True)
|
||||
manager_id = fields.Many2one('hr.employee', compute='_compute_from_employee_id', store=True, string='Manager')
|
||||
notes = fields.Html('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
|
||||
@@ -429,19 +430,25 @@ class HolidaysAllocation(models.Model):
|
||||
date=allocation.holiday_status_id.validity_stop
|
||||
))
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
""" Override to avoid automatic logging of creation """
|
||||
employee_id = values.get('employee_id', False)
|
||||
if not values.get('department_id'):
|
||||
values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id})
|
||||
holiday = super(HolidaysAllocation, self.with_context(mail_create_nosubscribe=True)).create(values)
|
||||
holiday.add_follower(employee_id)
|
||||
if holiday.validation_type == 'hr':
|
||||
holiday.message_subscribe(partner_ids=(holiday.employee_id.parent_id.user_id.partner_id | holiday.employee_id.leave_manager_id.partner_id).ids)
|
||||
if not self._context.get('import_file'):
|
||||
holiday.activity_update()
|
||||
return holiday
|
||||
for values in vals_list:
|
||||
employee_id = values.get('employee_id', False)
|
||||
if not values.get('department_id'):
|
||||
values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id})
|
||||
holidays = super(HolidaysAllocation, self.with_context(mail_create_nosubscribe=True)).create(vals_list)
|
||||
for holiday in holidays:
|
||||
partners_to_subscribe = set()
|
||||
if holiday.employee_id.user_id:
|
||||
partners_to_subscribe.add(holiday.employee_id.user_id.partner_id.id)
|
||||
if holiday.validation_type == 'hr':
|
||||
partners_to_subscribe.add(holiday.employee_id.parent_id.user_id.partner_id.id)
|
||||
partners_to_subscribe.add(holiday.employee_id.leave_manager_id.partner_id.id)
|
||||
holiday.message_subscribe(partner_ids=tuple(partners_to_subscribe))
|
||||
if not self._context.get('import_file'):
|
||||
holiday.activity_update()
|
||||
return holidays
|
||||
|
||||
def write(self, values):
|
||||
employee_id = values.get('employee_id', False)
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
<label for="number_of_days" string="Duration" attrs="{'invisible': [('allocation_type', '=', 'accrual')]}"/>
|
||||
<label for="number_of_days" string="Extra days" attrs="{'invisible': [('allocation_type', '!=', 'accrual')]}"/>
|
||||
</div>
|
||||
<div>
|
||||
<div name="duration_display">
|
||||
<field name="number_of_days_display" class="oe_inline" nolabel="1"
|
||||
attrs="{'readonly': ['|', ('type_request_unit', '=', 'hour'), ('state', 'not in', ('draft', 'confirm'))], 'invisible': [('type_request_unit', '=', 'hour')]}"/>
|
||||
<field name="number_of_hours_display" class="oe_inline" nolabel="1"
|
||||
|
||||
@@ -314,7 +314,7 @@
|
||||
<!-- When the user is leave manager, he should always see `number_of_days` to allow
|
||||
him to edit the value. `number_of_hours_display` is only an informative field -->
|
||||
<label for="number_of_days" string="Duration" attrs="{'invisible': [('request_unit_half', '=', True), ('leave_type_request_unit', '!=', 'hour')]}"/>
|
||||
<div>
|
||||
<div name="duration_display">
|
||||
<div class="o_row">
|
||||
<div groups="!hr_holidays.group_hr_holidays_manager" attrs="{'invisible': ['|', ('request_unit_half', '=', True), ('request_unit_hours', '=', True)]}" class="o_row">
|
||||
<field name="number_of_days_display" nolabel="1" readonly="1" class="oe_inline"/>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import tests
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': "HR Attendance Holidays",
|
||||
'summary': """""",
|
||||
'summary': """Attendance Holidays""",
|
||||
'category': 'Human Resources',
|
||||
'description': """
|
||||
Hides the attendance presence button when an employee is on leave.
|
||||
Convert employee's extra hours to leave allocations.
|
||||
""",
|
||||
'version': '1.0',
|
||||
'depends': ['hr_attendance', 'hr_holidays'],
|
||||
'auto_install': True,
|
||||
'data': [
|
||||
'security/hr_holidays_attendance_security.xml',
|
||||
'views/hr_leave_allocation_views.xml',
|
||||
'views/hr_leave_type_views.xml',
|
||||
'views/hr_leave_views.xml',
|
||||
'views/hr_employee_views.xml',
|
||||
'views/res_users_views.xml',
|
||||
'data/hr_holidays_attendance_data.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/hr_holidays_attendance_demo.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_qweb': [
|
||||
'hr_holidays_attendance/static/src/xml/time_off_calendar.xml',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="holiday_status_extra_hours" model="hr.leave.type">
|
||||
<field name="name">Extra Hours</field>
|
||||
<field name="request_unit">hour</field>
|
||||
<field name="allocation_type">no</field>
|
||||
<field name="leave_validation_type">manager</field>
|
||||
<field name="overtime_deductible" eval="True"/>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="base.main_company" model="res.company">
|
||||
<field name="hr_attendance_overtime" eval="True" />
|
||||
<field name="overtime_start_date" eval="(DateTime.today() + relativedelta(months=-1)).date()" />
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import hr_leave_allocation
|
||||
from . import hr_leave_type
|
||||
from . import hr_leave
|
||||
from . import res_company
|
||||
from . import res_users
|
||||
@@ -0,0 +1,81 @@
|
||||
# -*- 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 ValidationError
|
||||
from odoo.tools import float_round
|
||||
|
||||
|
||||
class HRLeave(models.Model):
|
||||
_inherit = 'hr.leave'
|
||||
|
||||
overtime_id = fields.Many2one('hr.attendance.overtime', string='Extra Hours', groups='hr_holidays.group_hr_holidays_user')
|
||||
employee_overtime = fields.Float(related='employee_id.total_overtime')
|
||||
overtime_deductible = fields.Boolean(compute='_compute_overtime_deductible')
|
||||
|
||||
@api.depends('holiday_status_id')
|
||||
def _compute_overtime_deductible(self):
|
||||
for leave in self:
|
||||
leave.overtime_deductible = leave.holiday_status_id.overtime_deductible and leave.holiday_status_id.allocation_type == 'no'
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
today = fields.Date.today()
|
||||
for leave in res:
|
||||
if not leave.overtime_deductible:
|
||||
continue
|
||||
employee = leave.employee_id.sudo()
|
||||
duration = leave.number_of_hours_display
|
||||
if duration > employee.total_overtime:
|
||||
if employee.user_id == self.env.user:
|
||||
raise ValidationError(_('You do not have enough extra hours to request this leave'))
|
||||
raise ValidationError(_('The employee does not have enough extra hours to request this leave.'))
|
||||
if not leave.overtime_id:
|
||||
leave.sudo().overtime_id = self.env['hr.attendance.overtime'].sudo().create({
|
||||
'employee_id': employee.id,
|
||||
'date': today,
|
||||
'adjustment': True,
|
||||
'duration': -1 * duration,
|
||||
})
|
||||
return res
|
||||
|
||||
def write(self, vals):
|
||||
res = super().write(vals)
|
||||
fields_to_check = {'number_of_days', 'date_from', 'date_to', 'state', 'employee_id'}
|
||||
if not any(field for field in fields_to_check if field in vals):
|
||||
return res
|
||||
#User may not have access to overtime_id field
|
||||
for leave in self.sudo().filtered('overtime_id'):
|
||||
employee = leave.employee_id
|
||||
duration = leave.number_of_hours_display
|
||||
overtime_duration = leave.overtime_id.sudo().duration
|
||||
if overtime_duration != duration:
|
||||
if duration > employee.total_overtime - overtime_duration:
|
||||
raise ValidationError(_('The employee does not have enough extra hours to extend this leave.'))
|
||||
leave.overtime_id.sudo().duration = -1 * duration
|
||||
return res
|
||||
|
||||
def action_draft(self):
|
||||
overtime_leaves = self.filtered('overtime_deductible')
|
||||
if any([l.employee_overtime < float_round(l.number_of_hours_display, 2) for l in overtime_leaves]):
|
||||
if self.employee_id.user_id.id == self.env.user.id:
|
||||
raise ValidationError(_('You do not have enough extra hours to request this leave'))
|
||||
raise ValidationError(_('The employee does not have enough extra hours to request this leave.'))
|
||||
|
||||
res = super().action_draft()
|
||||
overtime_leaves.overtime_id.sudo().unlink()
|
||||
for leave in overtime_leaves:
|
||||
overtime = self.env['hr.attendance.overtime'].sudo().create({
|
||||
'employee_id': leave.employee_id.id,
|
||||
'date': fields.Date.today(),
|
||||
'adjustment': True,
|
||||
'duration': -1 * leave.number_of_hours_display
|
||||
})
|
||||
leave.sudo().overtime_id = overtime.id
|
||||
return res
|
||||
|
||||
def action_refuse(self):
|
||||
res = super().action_refuse()
|
||||
self.overtime_id.sudo().unlink()
|
||||
return res
|
||||
@@ -0,0 +1,83 @@
|
||||
# -*- 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 ValidationError
|
||||
from odoo.tools import float_round
|
||||
|
||||
|
||||
class HolidaysAllocation(models.Model):
|
||||
_inherit = 'hr.leave.allocation'
|
||||
|
||||
def default_get(self, fields):
|
||||
res = super().default_get(fields)
|
||||
if 'holiday_status_id' in fields and self.env.context.get('deduct_extra_hours'):
|
||||
leave_type = self.env['hr.leave.type'].search([
|
||||
('valid', '=', True),
|
||||
('overtime_deductible', '=', True),
|
||||
('allocation_type', '!=', 'no')], limit=1)
|
||||
res['holiday_status_id'] = leave_type.id
|
||||
return res
|
||||
|
||||
overtime_deductible = fields.Boolean(compute='_compute_overtime_deductible')
|
||||
overtime_id = fields.Many2one('hr.attendance.overtime', string='Extra Hours', groups='hr_holidays.group_hr_holidays_user')
|
||||
employee_overtime = fields.Float(related='employee_id.total_overtime')
|
||||
hr_attendance_overtime = fields.Boolean(related='employee_company_id.hr_attendance_overtime')
|
||||
|
||||
@api.depends('holiday_status_id')
|
||||
def _compute_overtime_deductible(self):
|
||||
for allocation in self:
|
||||
allocation.overtime_deductible = allocation.holiday_status_id.company_id.hr_attendance_overtime and allocation.holiday_status_id.overtime_deductible
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
for allocation in res:
|
||||
if allocation.overtime_deductible and allocation.holiday_type == 'employee':
|
||||
duration = allocation.number_of_hours_display
|
||||
if duration > allocation.employee_id.total_overtime:
|
||||
raise ValidationError(_('The employee does not have enough overtime hours to request this leave.'))
|
||||
if not allocation.overtime_id:
|
||||
allocation.sudo().overtime_id = self.env['hr.attendance.overtime'].sudo().create({
|
||||
'employee_id': allocation.employee_id.id,
|
||||
'date': fields.Date.today(),
|
||||
'adjustment': True,
|
||||
'duration': -1 * duration,
|
||||
})
|
||||
return res
|
||||
|
||||
def write(self, vals):
|
||||
res = super().write(vals)
|
||||
if 'number_of_days' not in vals:
|
||||
return res
|
||||
for allocation in self.filtered('overtime_id'):
|
||||
employee = allocation.employee_id
|
||||
duration = allocation.number_of_hours_display
|
||||
overtime_duration = allocation.overtime_id.sudo().duration
|
||||
if overtime_duration != duration:
|
||||
if duration > employee.total_overtime - overtime_duration:
|
||||
raise ValidationError(_('The employee does not have enough extra hours to extend this allocation.'))
|
||||
allocation.overtime_id.sudo().duration = -1 * duration
|
||||
return res
|
||||
|
||||
def action_draft(self):
|
||||
overtime_allocations = self.filtered('overtime_deductible')
|
||||
if any([a.employee_overtime < float_round(a.number_of_hours_display, 2) for a in overtime_allocations]):
|
||||
raise ValidationError(_('The employee does not have enough extra hours to request this allocation.'))
|
||||
res = super().action_draft()
|
||||
|
||||
overtime_allocations.overtime_id.sudo().unlink()
|
||||
for allocation in overtime_allocations:
|
||||
overtime = self.env['hr.attendance.overtime'].sudo().create({
|
||||
'employee_id': allocation.employee_id.id,
|
||||
'date': fields.Date.today(),
|
||||
'adjustment': True,
|
||||
'duration': -1 * allocation.number_of_hours_display
|
||||
})
|
||||
allocation.sudo().overtime_id = overtime.id
|
||||
return res
|
||||
|
||||
def action_refuse(self):
|
||||
res = super().action_refuse()
|
||||
self.overtime_id.sudo().unlink()
|
||||
return res
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class HRLeaveType(models.Model):
|
||||
_inherit = 'hr.leave.type'
|
||||
|
||||
hr_attendance_overtime = fields.Boolean(related='company_id.hr_attendance_overtime')
|
||||
overtime_deductible = fields.Boolean(
|
||||
"Deduct Extra Hours", default=False,
|
||||
help="Once a time off of this type is approved, extra hours in attendances will be deducted.")
|
||||
|
||||
def get_employees_days(self, employee_ids):
|
||||
res = super().get_employees_days(employee_ids)
|
||||
deductible_time_off_type_ids = self.env['hr.leave.type'].search([
|
||||
('overtime_deductible', '=', True),
|
||||
('allocation_type', '=', 'no')]).ids
|
||||
for employee_id, allocations in res.items():
|
||||
for allocation_id in allocations:
|
||||
if allocation_id in deductible_time_off_type_ids:
|
||||
res[employee_id][allocation_id]['virtual_remaining_leaves'] = self.env['hr.employee'].sudo().browse(employee_id).total_overtime
|
||||
res[employee_id][allocation_id]['overtime_deductible'] = True
|
||||
else:
|
||||
res[employee_id][allocation_id]['overtime_deductible'] = False
|
||||
return res
|
||||
|
||||
def _get_days_request(self):
|
||||
res = super()._get_days_request()
|
||||
res[1]['overtime_deductible'] = self.overtime_deductible
|
||||
return res
|
||||
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
def write(self, vals):
|
||||
res = super().write(vals)
|
||||
if 'hr_attendance_overtime' not in vals:
|
||||
return res
|
||||
extra_hours_time_off_type = self.env.ref('hr_holidays_attendance.holiday_status_extra_hours', raise_if_not_found=False)
|
||||
if not extra_hours_time_off_type:
|
||||
return res
|
||||
all_companies = self.env['res.company'].sudo().search([])
|
||||
# Unarchive time of type if the feature is enabled
|
||||
if any(company.hr_attendance_overtime and not extra_hours_time_off_type.active for company in all_companies):
|
||||
extra_hours_time_off_type.toggle_active()
|
||||
# Archive time of type if the feature is disabled for all the company
|
||||
if all(not company.hr_attendance_overtime and extra_hours_time_off_type.active for company in all_companies):
|
||||
extra_hours_time_off_type.toggle_active()
|
||||
return res
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
request_overtime = fields.Boolean(compute='_compute_request_overtime')
|
||||
|
||||
@property
|
||||
def SELF_READABLE_FIELDS(self):
|
||||
return super().SELF_READABLE_FIELDS + ['request_overtime']
|
||||
|
||||
@api.depends_context('uid')
|
||||
@api.depends('total_overtime')
|
||||
def _compute_request_overtime(self):
|
||||
is_holiday_user = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
|
||||
time_off_types = self.env['hr.leave.type'].search_count([
|
||||
('valid', '=', True),
|
||||
('allocation_type', '=', 'fixed_allocation'),
|
||||
('overtime_deductible', '=', True)
|
||||
])
|
||||
for user in self:
|
||||
if user.total_overtime >= 1:
|
||||
if is_holiday_user:
|
||||
user.request_overtime = True
|
||||
else:
|
||||
user.request_overtime = time_off_types
|
||||
else:
|
||||
user.request_overtime = False
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="hr_holidays.group_hr_holidays_user" model="res.groups">
|
||||
<field name="implied_ids" eval="[(4, ref('hr_attendance.group_hr_attendance_user'))]"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-inherit="hr_holidays.dashboard_calendar_header" t-inherit-mode="extension">
|
||||
<!--inject a first condition-->
|
||||
<xpath expr="//t[@t-if='need_allocation']" position="before">
|
||||
<t t-if="timeoff[1]['overtime_deductible'] == true && !need_allocation">
|
||||
<span t-esc="timeoff[1]['virtual_remaining_leaves']" class="o_timeoff_big o_timeoff_green font-weight-bold"/> Hours <span class="o_timeoff_green">Available </span>
|
||||
</t>
|
||||
</xpath>
|
||||
<!--change t-if to t-elif-->
|
||||
<xpath expr="//t[@t-if='need_allocation']" position="attributes">
|
||||
<attribute name="t-if"/>
|
||||
<attribute name="t-elif">need_allocation</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
<t t-inherit="hr_holidays.dashboard_calendar_header_mobile" t-inherit-mode="extension">
|
||||
<!--inject a first condition-->
|
||||
<xpath expr="//t[@t-if='need_allocation']" position="before">
|
||||
<t t-if="timeoff[1]['overtime_deductible'] == true && !need_allocation">
|
||||
<strong t-esc="timeoff[1]['virtual_remaining_leaves']" class="o_timeoff_green"/> Hours <span class="o_timeoff_green ml-2">Available </span>
|
||||
</t>
|
||||
</xpath>
|
||||
<!--change t-if to t-elif-->
|
||||
<xpath expr="//t[@t-if='need_allocation']" position="attributes">
|
||||
<attribute name="t-if"/>
|
||||
<attribute name="t-elif">need_allocation</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_holidays_overtime
|
||||
@@ -0,0 +1,216 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo.tests import new_test_user
|
||||
from odoo.tests.common import TransactionCase, tagged
|
||||
|
||||
from odoo.exceptions import AccessError, ValidationError
|
||||
|
||||
@tagged('post_install', '-at_install', 'holidays_attendance')
|
||||
class TestHolidaysOvertime(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.company = self.env['res.company'].create({
|
||||
'name': 'SweatChipChop Inc.',
|
||||
'hr_attendance_overtime': True,
|
||||
'overtime_start_date': datetime(2021, 1, 1)
|
||||
})
|
||||
self.env.company = self.company
|
||||
self.user = new_test_user(self.env, login='user', groups='base.group_user,hr_attendance.group_hr_attendance', company_id=self.company.id)
|
||||
self.user_manager = new_test_user(self.env, login='manager', groups='base.group_user,hr_holidays.group_hr_holidays_user', company_id=self.company.id)
|
||||
|
||||
self.manager = self.env['hr.employee'].create({
|
||||
'name': 'Dominique',
|
||||
'user_id': self.user_manager.id,
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
self.employee = self.env['hr.employee'].create({
|
||||
'name': 'Barnabé',
|
||||
'user_id': self.user.id,
|
||||
'parent_id': self.manager.id,
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
|
||||
self.leave_type_no_alloc = self.env['hr.leave.type'].create({
|
||||
'name': 'Overtime Compensation No Allocation',
|
||||
'company_id': self.company.id,
|
||||
'allocation_type': 'no',
|
||||
'overtime_deductible': True,
|
||||
})
|
||||
self.leave_type_employee_allocation = self.env['hr.leave.type'].create({
|
||||
'name': 'Overtime Compensation Employee Allocation',
|
||||
'company_id': self.company.id,
|
||||
'allocation_type': 'fixed_allocation',
|
||||
'overtime_deductible': True,
|
||||
})
|
||||
self.leave_type_manager_alloc = self.env['hr.leave.type'].create({
|
||||
'name': 'Overtime Compensation Manager Allocation',
|
||||
'company_id': self.company.id,
|
||||
'allocation_type': 'fixed',
|
||||
'overtime_deductible': True,
|
||||
})
|
||||
|
||||
def new_attendance(self, check_in, check_out=False):
|
||||
return self.env['hr.attendance'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'check_in': check_in,
|
||||
'check_out': check_out,
|
||||
})
|
||||
|
||||
def test_deduct_button_visibility(self):
|
||||
with self.with_user('user'):
|
||||
self.assertFalse(self.user.request_overtime, 'Button should not be visible')
|
||||
|
||||
self.new_attendance(check_in=datetime(2021, 1, 2, 8), check_out=datetime(2021, 1, 2, 18))
|
||||
self.assertEqual(self.user.total_overtime, 10, 'Should have 10 hours of overtime')
|
||||
self.assertTrue(self.user.request_overtime, 'Button should be visible')
|
||||
|
||||
def test_check_overtime(self):
|
||||
with self.with_user('user'):
|
||||
self.assertEqual(self.user.total_overtime, 0, 'No overtime')
|
||||
|
||||
with self.assertRaises(ValidationError), self.cr.savepoint():
|
||||
self.env['hr.leave'].create({
|
||||
'name': 'no overtime',
|
||||
'employee_id': self.employee.id,
|
||||
'holiday_status_id': self.leave_type_no_alloc.id,
|
||||
'number_of_days': 1,
|
||||
'date_from': datetime(2021, 1, 4),
|
||||
'date_to': datetime(2021, 1, 5),
|
||||
'state': 'draft',
|
||||
})
|
||||
|
||||
self.new_attendance(check_in=datetime(2021, 1, 2, 8), check_out=datetime(2021, 1, 2, 16))
|
||||
self.assertEqual(self.employee.total_overtime, 8, 'Should have 8 hours of overtime')
|
||||
leave = self.env['hr.leave'].create({
|
||||
'name': 'no overtime',
|
||||
'employee_id': self.employee.id,
|
||||
'holiday_status_id': self.leave_type_no_alloc.id,
|
||||
'number_of_days': 1,
|
||||
'date_from': datetime(2021, 1, 4),
|
||||
'date_to': datetime(2021, 1, 5),
|
||||
})
|
||||
|
||||
# The employee doesn't have the right to read the overtime from the leave
|
||||
overtime = leave.sudo().overtime_id.with_user(self.user)
|
||||
|
||||
# An employee cannot delete an overtime adjustment
|
||||
with self.assertRaises(AccessError), self.cr.savepoint():
|
||||
overtime.unlink()
|
||||
|
||||
# ... nor change its duration
|
||||
with self.assertRaises(AccessError), self.cr.savepoint():
|
||||
overtime.duration = 8
|
||||
|
||||
def test_leave_adjust_overtime(self):
|
||||
self.new_attendance(check_in=datetime(2021, 1, 2, 8), check_out=datetime(2021, 1, 2, 16))
|
||||
self.assertEqual(self.employee.total_overtime, 8, 'Should have 8 hours of overtime')
|
||||
|
||||
leave = self.env['hr.leave'].create({
|
||||
'name': 'no overtime',
|
||||
'employee_id': self.employee.id,
|
||||
'holiday_status_id': self.leave_type_no_alloc.id,
|
||||
'number_of_days': 1,
|
||||
'date_from': datetime(2021, 1, 4),
|
||||
'date_to': datetime(2021, 1, 5),
|
||||
})
|
||||
|
||||
self.assertTrue(leave.overtime_id.adjustment, "An adjustment overtime should be created")
|
||||
self.assertEqual(leave.overtime_id.duration, -8)
|
||||
|
||||
self.assertEqual(self.employee.total_overtime, 0)
|
||||
|
||||
leave.action_refuse()
|
||||
self.assertFalse(leave.overtime_id.exists(), "Overtime should be deleted")
|
||||
self.assertEqual(self.employee.total_overtime, 8)
|
||||
|
||||
leave.action_draft()
|
||||
self.assertTrue(leave.overtime_id.exists(), "Overtime should be created")
|
||||
self.assertEqual(self.employee.total_overtime, 0)
|
||||
|
||||
def test_leave_check_overtime_write(self):
|
||||
self.new_attendance(check_in=datetime(2021, 1, 2, 8), check_out=datetime(2021, 1, 2, 16))
|
||||
self.new_attendance(check_in=datetime(2021, 1, 3, 8), check_out=datetime(2021, 1, 3, 16))
|
||||
self.assertEqual(self.employee.total_overtime, 16)
|
||||
|
||||
leave = self.env['hr.leave'].create({
|
||||
'name': 'no overtime',
|
||||
'employee_id': self.employee.id,
|
||||
'holiday_status_id': self.leave_type_no_alloc.id,
|
||||
'number_of_days': 1,
|
||||
'date_from': datetime(2021, 1, 4),
|
||||
'date_to': datetime(2021, 1, 5),
|
||||
})
|
||||
self.assertEqual(self.employee.total_overtime, 8)
|
||||
|
||||
leave.date_to = datetime(2021, 1, 6)
|
||||
self.assertEqual(self.employee.total_overtime, 0)
|
||||
with self.assertRaises(ValidationError), self.cr.savepoint():
|
||||
leave.date_to = datetime(2021, 1, 7)
|
||||
|
||||
leave.date_to = datetime(2021, 1, 5)
|
||||
self.assertEqual(self.employee.total_overtime, 8)
|
||||
|
||||
def test_employee_create_allocation(self):
|
||||
with self.with_user('user'):
|
||||
self.assertEqual(self.employee.total_overtime, 0)
|
||||
with self.assertRaises(ValidationError), self.cr.savepoint():
|
||||
self.env['hr.leave.allocation'].create({
|
||||
'name': 'test allocation',
|
||||
'holiday_status_id': self.leave_type_employee_allocation.id,
|
||||
'employee_id': self.employee.id,
|
||||
'number_of_days': 1,
|
||||
'state': 'draft',
|
||||
})
|
||||
|
||||
self.new_attendance(check_in=datetime(2021, 1, 2, 8), check_out=datetime(2021, 1, 2, 16))
|
||||
self.assertAlmostEqual(self.employee.total_overtime, 8, 'Should have 8 hours of overtime')
|
||||
|
||||
allocation = self.env['hr.leave.allocation'].create({
|
||||
'name': 'test allocation',
|
||||
'holiday_status_id': self.leave_type_employee_allocation.id,
|
||||
'employee_id': self.employee.id,
|
||||
'number_of_days': 1,
|
||||
'state': 'draft',
|
||||
})
|
||||
allocation.action_confirm()
|
||||
self.assertEqual(self.employee.total_overtime, 0)
|
||||
|
||||
leave_type = self.env['hr.leave.type'].sudo().create({
|
||||
'name': 'Overtime Compensation Employee Allocation',
|
||||
'company_id': self.company.id,
|
||||
'allocation_type': 'fixed_allocation',
|
||||
'overtime_deductible': False,
|
||||
})
|
||||
|
||||
# User can request another allocation even without overtime
|
||||
allocation2 = self.env['hr.leave.allocation'].create({
|
||||
'name': 'test allocation',
|
||||
'holiday_status_id': leave_type.id,
|
||||
'employee_id': self.employee.id,
|
||||
'number_of_days': 1,
|
||||
'state': 'draft',
|
||||
})
|
||||
allocation2.action_confirm()
|
||||
|
||||
def test_allocation_check_overtime_write(self):
|
||||
self.new_attendance(check_in=datetime(2021, 1, 2, 8), check_out=datetime(2021, 1, 2, 16))
|
||||
self.new_attendance(check_in=datetime(2021, 1, 3, 8), check_out=datetime(2021, 1, 3, 16))
|
||||
self.assertEqual(self.employee.total_overtime, 16, 'Should have 16 hours of overtime')
|
||||
|
||||
alloc = self.env['hr.leave.allocation'].create({
|
||||
'name': 'test allocation',
|
||||
'holiday_status_id': self.leave_type_employee_allocation.id,
|
||||
'employee_id': self.employee.id,
|
||||
'number_of_days': 1,
|
||||
'state': 'draft',
|
||||
})
|
||||
self.assertEqual(self.employee.total_overtime, 8)
|
||||
|
||||
with self.assertRaises(ValidationError), self.cr.savepoint():
|
||||
alloc.number_of_days = 3
|
||||
|
||||
alloc.number_of_days = 2
|
||||
self.assertEqual(self.employee.total_overtime, 0)
|
||||
@@ -31,6 +31,23 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.holidays.attendance.employee.view.form.inherit</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//header" position="inside">
|
||||
<button
|
||||
name="%(hr_leave_allocation_overtime_manager_action)d"
|
||||
string="Deduct Extra Hours"
|
||||
type="action"
|
||||
groups="hr_holidays.group_hr_holidays_user"
|
||||
context="{'default_employee_id': id, 'deduct_extra_hours': True}"
|
||||
attrs="{'invisible': [('total_overtime', '<=', 1)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.employee.holidays.attendance.inherit</field>
|
||||
<field name="model">hr.employee</field>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?xml version='1.0' encoding='UTF-8' ?>
|
||||
<odoo>
|
||||
<record id="hr_attendance_holidays_hr_leave_allocation_view_form_inherit" model="ir.ui.view">
|
||||
<field name="model">hr.leave.allocation</field>
|
||||
<field name="inherit_id" ref="hr_holidays.hr_leave_allocation_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="(//div[@name='duration_display']/span)[last()]" position="after">
|
||||
<field name="hr_attendance_overtime" invisible="1" />
|
||||
<field name="overtime_deductible" invisible="1" />
|
||||
<field name="employee_overtime" invisible="1" />
|
||||
<div class="oe_inline"
|
||||
attrs="{'invisible': ['|', '|', '|',
|
||||
('hr_attendance_overtime', '=', False), ('employee_id', '=', False),
|
||||
('overtime_deductible', '=', False), ('employee_overtime', '<=', 0)]}">
|
||||
<field name="employee_overtime" nolabel="1" widget="float_time" class="text-success" /> Extra Hours Available
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_leave_allocation_overtime_view_form" model="ir.ui.view">
|
||||
<field name="model">hr.leave.allocation</field>
|
||||
<field name="inherit_id" ref="hr_attendance_holidays_hr_leave_allocation_view_form_inherit" />
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">60</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//header" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<div name="button_box" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</div>
|
||||
<xpath expr="//field[@name='holiday_status_id']" position="attributes">
|
||||
<attribute name="domain">[('valid', '=', True), ('overtime_deductible', '=', True), ('allocation_type', '=', 'fixed_allocation')]</attribute>
|
||||
<attribute name="options">{'no_create': True, 'no_open': True}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//sheet" position="after">
|
||||
<footer>
|
||||
<button string="Save" special="save" class="btn btn-primary" close="1" />
|
||||
<button string="Discard" special="cancel" class="btn-secondary" close="1" />
|
||||
</footer>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_leave_allocation_overtime_manager_view_form" model="ir.ui.view">
|
||||
<field name="model">hr.leave.allocation</field>
|
||||
<field name="inherit_id" ref="hr_leave_allocation_overtime_view_form" />
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">70</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='holiday_status_id']" position="attributes">
|
||||
<attribute name="domain">[('valid', '=', True), ('overtime_deductible', '=', True), ('allocation_type', '!=', 'no')]</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_leave_allocation_overtime_action" model="ir.actions.act_window">
|
||||
<field name="name">New Allocation Request</field>
|
||||
<field name="res_model">hr.leave.allocation</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0), (0, 0, {'view_mode': 'form', 'view_id': ref('hr_leave_allocation_overtime_view_form')})]"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_leave_allocation_overtime_manager_action" model="ir.actions.act_window">
|
||||
<field name="name">New Allocation Request</field>
|
||||
<field name="res_model">hr.leave.allocation</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0), (0, 0, {'view_mode': 'form', 'view_id': ref('hr_leave_allocation_overtime_manager_view_form')})]"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id="hr_leave_type_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.leave.type.view.form.inherit</field>
|
||||
<field name="model">hr.leave.type</field>
|
||||
<field name="inherit_id" ref="hr_holidays.edit_holiday_status_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='leave_validation_type']" position="after">
|
||||
<field name="hr_attendance_overtime" invisible="1" />
|
||||
<field name="overtime_deductible" attrs="{'invisible': [('hr_attendance_overtime', '=', False)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version='1.0' encoding='UTF-8' ?>
|
||||
<odoo>
|
||||
<record id="hr_leave_view_form" model="ir.ui.view">
|
||||
<field name="model">hr.leave</field>
|
||||
<field name="inherit_id" ref="hr_holidays.hr_leave_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="(//div[@name='duration_display']/div)[last()]" position="after">
|
||||
<field name="overtime_deductible" invisible="1" />
|
||||
<field name="employee_overtime" invisible="1" />
|
||||
<div attrs="{'invisible': ['|', '|', ('employee_id', '=', False), ('overtime_deductible', '=', False), ('employee_overtime', '<=', 0)]}">
|
||||
<field name="employee_overtime" nolabel="1" widget="float_time" class="text-success" /> Extra Hours Available
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="res_users_view_form" model="ir.ui.view">
|
||||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="hr.res_users_view_form_profile"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//header" position="inside">
|
||||
<field name="employee_id" invisible="1" />
|
||||
<field name="request_overtime" invisible="1" />
|
||||
<button
|
||||
name="%(hr_leave_allocation_overtime_action)d"
|
||||
string="Deduct Extra Hours"
|
||||
type="action"
|
||||
context="{'default_employee_id': employee_id, 'deduct_extra_hours': True}"
|
||||
attrs="{'invisible': [('request_overtime', '=', False)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -47,7 +47,7 @@ class TestWorkEntryHolidaysPerformance(TestWorkEntryHolidaysBase):
|
||||
@users('__system__', 'admin')
|
||||
@warmup
|
||||
def test_performance_leave_create(self):
|
||||
with self.assertQueryCount(__system__=22, admin=23):
|
||||
with self.assertQueryCount(__system__=24, admin=25):
|
||||
leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0))
|
||||
leave.action_refuse()
|
||||
|
||||
@@ -56,7 +56,7 @@ class TestWorkEntryHolidaysPerformance(TestWorkEntryHolidaysBase):
|
||||
def test_performance_leave_confirm(self):
|
||||
leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0))
|
||||
leave.action_draft()
|
||||
with self.assertQueryCount(__system__=20, admin=21):
|
||||
with self.assertQueryCount(__system__=22, admin=23):
|
||||
leave.action_confirm()
|
||||
leave.state = 'cancel'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user