oca-ocb-hr/odoo-bringout-oca-ocb-hr_attendance/hr_attendance/models/hr_attendance_overtime.py
Ernad Husremovic e1d89e11e3 19.0 vanilla
2026-03-09 09:31:00 +01:00

108 lines
4.2 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models, fields
class HrAttendanceOvertimeLine(models.Model):
_name = 'hr.attendance.overtime.line'
_description = "Attendance Overtime Line"
_rec_name = 'employee_id'
_order = 'time_start'
employee_id = fields.Many2one(
'hr.employee', string="Employee",
required=True, ondelete='cascade', index=True)
company_id = fields.Many2one(related='employee_id.company_id')
date = fields.Date(string='Day', index=True, required=True)
status = fields.Selection([
('to_approve', "To Approve"),
('approved', "Approved"),
('refused', "Refused")
],
compute='_compute_status',
required=True, store=True, readonly=False, precompute=True,
)
duration = fields.Float(string='Extra Hours', default=0.0, required=True)
manual_duration = fields.Float( # TODO -> real_duration for easier upgrade
string='Extra Hours (encoded)',
compute='_compute_manual_duration',
store=True, readonly=False,
)
time_start = fields.Datetime(string='Start') # time_start will be equal to attendance.check_in
time_stop = fields.Datetime(string='Stop') # time_stop will be equal to attendance.check_out
amount_rate = fields.Float("Overtime pay rate", required=True, default=1.0)
is_manager = fields.Boolean(compute="_compute_is_manager")
rule_ids = fields.Many2many("hr.attendance.overtime.rule", string="Applied Rules")
# in payroll: rate, work_entry_type
# in time_off: convertible_to_time_off
# Check no overlapping overtimes for the same employee.
# Technical explanation: Exclude constraints compares the given expression on rows 2 by 2 using the given operator; && on tsrange is the intersection.
# cf: https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-EXCLUSION
# for employee_id we compare [employee_id -> employee_id] ranges bc raw integer is not supported (?)
# _overtime_no_overlap_same_employee = models.Constraint("""
# EXCLUDE USING GIST (
# tsrange(time_start, time_stop, '()') WITH &&,
# int4range(employee_id, employee_id, '[]') WITH =
# )
# """,
# "Employee cannot have overlapping overtimes",
# )
_overtime_start_before_end = models.Constraint(
'CHECK (time_stop > time_start)',
'Starting time should be before end time.',
)
@api.depends('employee_id')
def _compute_status(self):
for overtime in self:
if not overtime.status:
overtime.status = 'to_approve' if overtime.employee_id.company_id.attendance_overtime_validation == 'by_manager' else 'approved'
@api.depends('duration')
def _compute_manual_duration(self):
for overtime in self:
overtime.manual_duration = overtime.duration
@api.depends('employee_id')
def _compute_is_manager(self):
has_manager_right = self.env.user.has_group('hr_attendance.group_hr_attendance_manager')
has_officer_right = self.env.user.has_group('hr_attendance.group_hr_attendance_officer')
for overtime in self:
overtime.is_manager = (
has_manager_right or
(
has_officer_right
and overtime.employee_id.attendance_manager_id == self.env.user
)
)
def action_approve(self):
self.write({'status': 'approved'})
def action_refuse(self):
self.write({'status': 'refused'})
def _linked_attendances(self):
return self.env['hr.attendance'].search([
('check_in', 'in', self.mapped('time_start')),
('employee_id', 'in', self.employee_id.ids),
])
def write(self, vals):
if any(key in vals for key in ['status', 'manual_duration']):
attendances = self._linked_attendances()
self.env.add_to_compute(
attendances._fields['overtime_status'],
attendances
)
self.env.add_to_compute(
attendances._fields['validated_overtime_hours'],
attendances
)
return super().write(vals)