[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:
Kevin Baptiste
2021-07-06 11:35:01 +02:00
committed by William Braeckman
parent 9e6d2da504
commit 2961513141
23 changed files with 723 additions and 18 deletions
@@ -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"
+1 -1
View File
@@ -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
+17 -2
View File
@@ -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 &amp;&amp; !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 &amp;&amp; !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', '&lt;=', 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', '&lt;=', 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', '&lt;=', 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'