1# -*- coding: utf-8 -*- 2# Part of Odoo. See LICENSE file for full copyright and licensing details. 3 4from odoo import models, fields, api, exceptions, _ 5from odoo.tools import format_datetime 6 7 8class HrAttendance(models.Model): 9 _name = "hr.attendance" 10 _description = "Attendance" 11 _order = "check_in desc" 12 13 def _default_employee(self): 14 return self.env.user.employee_id 15 16 employee_id = fields.Many2one('hr.employee', string="Employee", default=_default_employee, required=True, ondelete='cascade', index=True) 17 department_id = fields.Many2one('hr.department', string="Department", related="employee_id.department_id", 18 readonly=True) 19 check_in = fields.Datetime(string="Check In", default=fields.Datetime.now, required=True) 20 check_out = fields.Datetime(string="Check Out") 21 worked_hours = fields.Float(string='Worked Hours', compute='_compute_worked_hours', store=True, readonly=True) 22 23 def name_get(self): 24 result = [] 25 for attendance in self: 26 if not attendance.check_out: 27 result.append((attendance.id, _("%(empl_name)s from %(check_in)s") % { 28 'empl_name': attendance.employee_id.name, 29 'check_in': format_datetime(self.env, attendance.check_in, dt_format=False), 30 })) 31 else: 32 result.append((attendance.id, _("%(empl_name)s from %(check_in)s to %(check_out)s") % { 33 'empl_name': attendance.employee_id.name, 34 'check_in': format_datetime(self.env, attendance.check_in, dt_format=False), 35 'check_out': format_datetime(self.env, attendance.check_out, dt_format=False), 36 })) 37 return result 38 39 @api.depends('check_in', 'check_out') 40 def _compute_worked_hours(self): 41 for attendance in self: 42 if attendance.check_out and attendance.check_in: 43 delta = attendance.check_out - attendance.check_in 44 attendance.worked_hours = delta.total_seconds() / 3600.0 45 else: 46 attendance.worked_hours = False 47 48 @api.constrains('check_in', 'check_out') 49 def _check_validity_check_in_check_out(self): 50 """ verifies if check_in is earlier than check_out. """ 51 for attendance in self: 52 if attendance.check_in and attendance.check_out: 53 if attendance.check_out < attendance.check_in: 54 raise exceptions.ValidationError(_('"Check Out" time cannot be earlier than "Check In" time.')) 55 56 @api.constrains('check_in', 'check_out', 'employee_id') 57 def _check_validity(self): 58 """ Verifies the validity of the attendance record compared to the others from the same employee. 59 For the same employee we must have : 60 * maximum 1 "open" attendance record (without check_out) 61 * no overlapping time slices with previous employee records 62 """ 63 for attendance in self: 64 # we take the latest attendance before our check_in time and check it doesn't overlap with ours 65 last_attendance_before_check_in = self.env['hr.attendance'].search([ 66 ('employee_id', '=', attendance.employee_id.id), 67 ('check_in', '<=', attendance.check_in), 68 ('id', '!=', attendance.id), 69 ], order='check_in desc', limit=1) 70 if last_attendance_before_check_in and last_attendance_before_check_in.check_out and last_attendance_before_check_in.check_out > attendance.check_in: 71 raise exceptions.ValidationError(_("Cannot create new attendance record for %(empl_name)s, the employee was already checked in on %(datetime)s") % { 72 'empl_name': attendance.employee_id.name, 73 'datetime': format_datetime(self.env, attendance.check_in, dt_format=False), 74 }) 75 76 if not attendance.check_out: 77 # if our attendance is "open" (no check_out), we verify there is no other "open" attendance 78 no_check_out_attendances = self.env['hr.attendance'].search([ 79 ('employee_id', '=', attendance.employee_id.id), 80 ('check_out', '=', False), 81 ('id', '!=', attendance.id), 82 ], order='check_in desc', limit=1) 83 if no_check_out_attendances: 84 raise exceptions.ValidationError(_("Cannot create new attendance record for %(empl_name)s, the employee hasn't checked out since %(datetime)s") % { 85 'empl_name': attendance.employee_id.name, 86 'datetime': format_datetime(self.env, no_check_out_attendances.check_in, dt_format=False), 87 }) 88 else: 89 # we verify that the latest attendance with check_in time before our check_out time 90 # is the same as the one before our check_in time computed before, otherwise it overlaps 91 last_attendance_before_check_out = self.env['hr.attendance'].search([ 92 ('employee_id', '=', attendance.employee_id.id), 93 ('check_in', '<', attendance.check_out), 94 ('id', '!=', attendance.id), 95 ], order='check_in desc', limit=1) 96 if last_attendance_before_check_out and last_attendance_before_check_in != last_attendance_before_check_out: 97 raise exceptions.ValidationError(_("Cannot create new attendance record for %(empl_name)s, the employee was already checked in on %(datetime)s") % { 98 'empl_name': attendance.employee_id.name, 99 'datetime': format_datetime(self.env, last_attendance_before_check_out.check_in, dt_format=False), 100 }) 101 102 @api.returns('self', lambda value: value.id) 103 def copy(self): 104 raise exceptions.UserError(_('You cannot duplicate an attendance.')) 105