Initial commit: Hr packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:50 +02:00
commit 62531cd146
2820 changed files with 1432848 additions and 0 deletions

View file

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import hr_attendance
from . import hr_leave_allocation
from . import hr_leave_type
from . import hr_leave
from . import res_company
from . import res_users

View file

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo.osv.expression import AND
class HrAttendance(models.Model):
_inherit = "hr.attendance"
def _get_overtime_leave_domain(self):
domain = super()._get_overtime_leave_domain()
# resource_id = False => Public holidays
return AND([domain, ['|', ('holiday_id.holiday_status_id.time_type', '=', 'leave'), ('resource_id', '=', False)]])

View file

@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from datetime import timedelta
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.requires_allocation == 'no'
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
self._check_overtime_deductible(res)
return res
def write(self, vals):
res = super().write(vals)
fields_to_check = {'number_of_days', 'date_from', 'date_to', 'state', 'employee_id', 'holiday_status_id'}
if not any(field for field in fields_to_check if field in vals):
return res
if vals.get('holiday_status_id'):
self._check_overtime_deductible(self)
#User may not have access to overtime_id field
for leave in self.sudo().filtered('overtime_id'):
# It must always be possible to refuse leave based on overtime
if vals.get('state') in ['refuse']:
continue
employee = leave.employee_id
duration = leave.number_of_hours_display
overtime_duration = leave.overtime_id.sudo().duration
if overtime_duration != -1 * 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 _check_overtime_deductible(self, leaves):
# If the type of leave is overtime deductible, we have to check that the employee has enough extra hours
for leave in leaves:
if not leave.overtime_deductible:
leave.sudo().overtime_id.unlink()
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.sudo().overtime_id:
leave.sudo().overtime_id = self.env['hr.attendance.overtime'].sudo().create({
'employee_id': employee.id,
'date': leave.date_from,
'adjustment': True,
'duration': -1 * duration,
})
def action_draft(self):
overtime_leaves = self.filtered('overtime_deductible')
res = super().action_draft()
overtime_leaves.overtime_id.sudo().unlink()
return res
def action_confirm(self):
res = super().action_confirm()
self._check_overtime_deductible(self)
return res
def action_refuse(self):
res = super().action_refuse()
self.sudo().overtime_id.unlink()
return res
def _validate_leave_request(self):
super()._validate_leave_request()
self._update_leaves_overtime()
def _remove_resource_leave(self):
res = super()._remove_resource_leave()
self._update_leaves_overtime()
return res
def _update_leaves_overtime(self):
employee_dates = defaultdict(set)
for leave in self:
if leave.employee_id and leave.employee_company_id.hr_attendance_overtime:
for d in range((leave.date_to - leave.date_from).days + 1):
employee_dates[leave.employee_id].add(self.env['hr.attendance']._get_day_start_and_day(leave.employee_id, leave.date_from + timedelta(days=d)))
if employee_dates:
self.env['hr.attendance']._update_overtime(employee_dates)
def unlink(self):
# TODO master change to ondelete
self.sudo().overtime_id.unlink()
return super().unlink()
def _force_cancel(self, *args, **kwargs):
super()._force_cancel(*args, **kwargs)
self.sudo().overtime_id.unlink()

View file

@ -0,0 +1,87 @@
# -*- 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
from odoo.osv import expression
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'):
domain = [('overtime_deductible', '=', True), ('requires_allocation', '=', 'yes')]
if self.env.context.get('deduct_extra_hours_employee_request', False):
# Prevent loading manager allocated time off type in self request contexts
domain = expression.AND([domain, [('employee_requests', '=', 'yes')]])
leave_type = self.env['hr.leave.type'].search(domain, 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.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': allocation.date_from,
'adjustment': True,
'duration': -1 * duration,
})
return res
def write(self, vals):
res = super().write(vals)
if 'number_of_days' not in vals:
return res
if not self.env.user.has_group("hr_holidays.group_hr_holidays_user") and any(allocation.state not in ('draft', 'confirm') for allocation in self):
raise ValidationError(_('Only an Officer or Administrator is allowed to edit the allocation duration in this status.'))
for allocation in self.sudo().filtered('overtime_id'):
employee = allocation.employee_id
duration = allocation.number_of_hours_display
overtime_duration = allocation.overtime_id.sudo().duration
if overtime_duration != -1 * 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': allocation.date_from,
'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

View file

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tools.misc import format_duration
from odoo import _, api, fields, models
class HRLeaveType(models.Model):
_inherit = 'hr.leave.type'
hr_attendance_overtime = fields.Boolean(compute='_compute_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 name_get(self):
# Exclude hours available in allocation contexts, it might be confusing otherwise
if not self.requested_name_get() or self._context.get('request_type', 'leave') == 'allocation':
return super().name_get()
employee_id = False
overtime_leaves = self.env['hr.leave.type']
res = []
for leave_type in self:
if leave_type.overtime_deductible and leave_type.requires_allocation == 'no':
if not employee_id:
employee_id = self.env['hr.employee'].browse(self._context.get('employee_id')).sudo()
if employee_id.total_overtime > 0:
overtime_leaves |= leave_type
name = "%(name)s (%(count)s)" % {
'name': leave_type.name,
'count': _('%s hours available',
format_duration(employee_id.total_overtime)),
}
res.append((leave_type.id, name))
res += super(HRLeaveType, self - overtime_leaves).name_get()
return res
def get_employees_days(self, employee_ids, date=None):
res = super().get_employees_days(employee_ids, date)
deductible_time_off_type_ids = self.env['hr.leave.type'].search([
('overtime_deductible', '=', True),
('requires_allocation', '=', '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
@api.depends('company_id.hr_attendance_overtime')
def _compute_hr_attendance_overtime(self):
# If no company is linked to the time off type, use the current company's setting
for leave_type in self:
if leave_type.company_id:
leave_type.hr_attendance_overtime = leave_type.company_id.hr_attendance_overtime
else:
leave_type.hr_attendance_overtime = self.env.company.hr_attendance_overtime

View file

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class ResCompany(models.Model):
_inherit = 'res.company'
@api.model
def _check_extra_hours_time_off(self):
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
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()
def write(self, vals):
res = super().write(vals)
if 'hr_attendance_overtime' in vals:
self._check_extra_hours_time_off()
return res

View file

@ -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([
('requires_allocation', '=', 'yes'),
('employee_requests', '=', 'yes'),
('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