odex30_standard/to_attendance_device/models/user_attendance.py

249 lines
14 KiB
Python

import logging
from odoo import models, fields, api, _
from odoo import tools
_logger = logging.getLogger(__name__)
class UserAttendance(models.Model):
_name = 'user.attendance'
_description = 'User Attendance'
_order = 'timestamp DESC, user_id, status, attendance_state_id, device_id'
device_id = fields.Many2one('attendance.device', string='Attendance Machine', required=True, ondelete='restrict', index=True)
user_id = fields.Many2one('attendance.device.user', string='Machine User', required=True, ondelete='cascade', index=True)
timestamp = fields.Datetime(string='Timestamp', required=True, index=True,
help="The date and time at which the employee took a check in/out action at the attendance machine")
status = fields.Integer(string='Machine Attendance State', required=True,
help="The state which is the unique number stored in the machine to"
" indicate type of attendance (e.g. 0: Checkin, 1: Checkout, etc)")
attendance_state_id = fields.Many2one('attendance.state', string='Software Attendance State',
help="This technical field is to map the attendance"
" status stored in the machine and the attendance status in System", required=True, index=True)
activity_id = fields.Many2one('attendance.activity', related='attendance_state_id.activity_id', store=True, index=True)
hr_attendance_id = fields.Many2one('hr.attendance', string='HR Attendance', ondelete='set null',
help="The technical field to link Machine Attendance Data with System Attendance Data", index=True)
type = fields.Selection(string='Activity Type', related='attendance_state_id.type', store=True)
employee_id = fields.Many2one('hr.employee', string='Employee', related='user_id.employee_id', store=True, index=True)
synced = fields.Boolean(string='Synced', help="This field is to indicate whether the attendance data is synchronized to System or not")
_sql_constraints = [
('unique_user_id_device_id_timestamp',
'UNIQUE(user_id, device_id, timestamp)',
"The Timestamp and User must be unique per machine"),
]
@api.model_create_multi
def create(self, vals_list):
attendances = super(UserAttendance, self).create(vals_list)
return attendances
@api.constrains('status', 'attendance_state_id')
def constrains_status_attendance_state_id(self):
for r in self:
if r.status != r.attendance_state_id.code:
raise(_("Attendance Status conflict! The status number from machine must match the attendance status defined in System."))
# def _prepare_last_hr_attendance_domain(self):
# self.ensure_one()
# return [
# ('employee_id', '=', self.employee_id.id),
# ('check_in', '<=', self.timestamp),
# '|', ('activity_id', '=', False), ('activity_id', '=', self.activity_id.id),
# ]
def _prepare_last_hr_attendance_domain(self):
self.ensure_one()
return [
('employee_id', '=', self.employee_id.id),
('check_in', '<=', self.timestamp),
'|', ('activity_id', '=', False), ('activity_id', '=', self.activity_id.id),
]
# def _get_last_hr_attendance(self, user_attendance, hr_attendances):
# last_hr_attendance = hr_attendances.filtered(
# lambda hr_att: hr_att.employee_id == user_attendance.employee_id
# and hr_att.checkin_device_id
# and hr_att.check_in <= user_attendance.timestamp
# and (hr_att.activity_id == user_attendance.activity_id
# or not hr_att.activity_id)).sorted('check_in')
# return last_hr_attendance and last_hr_attendance[-1:] or False
def _get_last_hr_attendance(self, user_attendance, hr_attendances):
# Filter by same day to ensure check-in and check-out are linked for the same day
same_day_attendance = hr_attendances.filtered(
lambda hr_att: hr_att.employee_id == user_attendance.employee_id
and hr_att.check_in.date() == user_attendance.timestamp.date()
and (hr_att.activity_id == user_attendance.activity_id
or not hr_att.activity_id)
).sorted('check_in')
return same_day_attendance and same_day_attendance[-1:] or False
# def _prepare_hr_attendance_vals(self):
# return {
# 'employee_id': self.employee_id.id,
# 'check_in': self.timestamp,
# 'checkin_device_id': self.device_id.id,
# 'activity_id': self.activity_id.id,
# }
#
# def _create_hr_attendance(self):
# vals_list = []
# for r in self:
# vals_list.append(r._prepare_hr_attendance_vals())
# return self.env['hr.attendance'].with_context(sync_from_device=True).create(vals_list)
def _prepare_hr_attendance_vals(self):
return {
'employee_id': self.employee_id.id,
'check_in': self.timestamp,
'checkin_device_id': self.device_id.id,
'activity_id': self.activity_id.id,
}
def _create_hr_attendance(self):
vals_list = [r._prepare_hr_attendance_vals() for r in self]
return self.env['hr.attendance'].with_context(sync_from_device=True).create(vals_list)
# def _sync_attendance(self):
# """
# This method synchronizes `user.attendance` data into `hr.attendance` data
# """
# error_msg = {}
# all_hr_attendances = self.env['hr.attendance'].search([('employee_id', 'in', self.employee_id.ids)])
# for attendance_activity in self.activity_id:
# for employee in self.filtered(lambda r: r.activity_id == attendance_activity).employee_id:
# unsync_user_attendances = self.filtered(
# lambda uatt: uatt.employee_id == employee
# and uatt.activity_id == attendance_activity
# ).sorted('timestamp')
# hr_attendances = all_hr_attendances.filtered(
# lambda hr_att: hr_att.employee_id == employee
# )
# for uatt in unsync_user_attendances:
# # Do flush manually to avoid magical user error:
# # https://github.com/Viindoo/tvtmaaddons/pull/4053
# try:
# with self.env.cr.savepoint(flush=False), tools.mute_logger('odoo.sql_db'):
# last_hr_attendance = self._get_last_hr_attendance(uatt, hr_attendances)
# uatt_update = False
# if uatt.type == 'checkin':
# if not last_hr_attendance or (last_hr_attendance.check_out and uatt.timestamp > last_hr_attendance.check_out):
# last_hr_attendance = uatt._create_hr_attendance()
# uatt_update = True
# else:
# if last_hr_attendance and not last_hr_attendance.check_out and uatt.timestamp >= last_hr_attendance.check_in:
# last_hr_attendance.with_context(not_manual_check_out_modification=True, sync_from_device=True).write({
# 'check_out': uatt.timestamp,
# 'checkout_device_id': uatt.device_id.id
# })
# uatt_update = True
# uatt_vals_to_update = {'synced': True}
# if uatt_update:
# if last_hr_attendance not in hr_attendances:
# hr_attendances += last_hr_attendance
# uatt_vals_to_update.update({'hr_attendance_id': last_hr_attendance.id})
# uatt.write(uatt_vals_to_update)
# self.flush_recordset()
# except Exception as e:
# all_hr_attendances = self.env['hr.attendance'].search([('employee_id', 'in', self.employee_id.ids)])
# error_msg.setdefault(uatt.device_id, [])
# msg = str(e)
# if msg not in error_msg[uatt.device_id]:
# error_msg[uatt.device_id].append(str(e))
# if bool(error_msg):
# for device, msg_list in error_msg.items():
# device.message_post(body="<ol>%s</ol>" % "".join(["<li>%s</li>" % msg for msg in msg_list]))
def _sync_attendance(self):
error_msg = {}
all_hr_attendances = self.env['hr.attendance'].search([
('employee_id', 'in', self.employee_id.ids)])
for attendance_activity in self.activity_id:
for employee in self.filtered(lambda r: r.activity_id == attendance_activity).employee_id:
unsync_user_attendances = self.filtered(
lambda uatt: uatt.employee_id == employee
and uatt.activity_id == attendance_activity
).sorted('timestamp')
hr_attendances = all_hr_attendances.filtered(
lambda hr_att: hr_att.employee_id == employee
)
for uatt in unsync_user_attendances:
try:
with self.env.cr.savepoint(flush=False), tools.mute_logger('odoo.sql_db'):
last_hr_attendance = self._get_last_hr_attendance(uatt, hr_attendances)
uatt_update = False
if uatt.type == 'checkin':
if last_hr_attendance:
# If the last entry has no checkout, update it
if not last_hr_attendance.check_out and uatt.timestamp > last_hr_attendance.check_in:
last_hr_attendance.with_context(
not_manual_check_out_modification=True,
sync_from_device=True
).write({'check_out': uatt.timestamp, 'checkout_device_id': uatt.device_id.id})
uatt_update = True
else:
# Create new check-in if previous one is completed
last_hr_attendance = uatt._create_hr_attendance()
uatt_update = True
else:
# No existing attendance, create new
last_hr_attendance = uatt._create_hr_attendance()
uatt_update = True
else:
# Handle check-out
if last_hr_attendance and not last_hr_attendance.check_out and uatt.timestamp >= last_hr_attendance.check_in:
last_hr_attendance.with_context(
not_manual_check_out_modification=True,
sync_from_device=True
).write({'check_out': uatt.timestamp, 'checkout_device_id': uatt.device_id.id})
uatt_update = True
elif not last_hr_attendance:
# No check-in exists, create both check-in and check-out
last_hr_attendance = uatt._create_hr_attendance()
last_hr_attendance.write(
{'check_out': uatt.timestamp, 'checkout_device_id': uatt.device_id.id})
uatt_update = True
# Update user attendance record
uatt_vals_to_update = {'synced': True}
if uatt_update:
if last_hr_attendance not in hr_attendances:
hr_attendances += last_hr_attendance
uatt_vals_to_update.update({'hr_attendance_id': last_hr_attendance.id})
uatt.write(uatt_vals_to_update)
self.flush_recordset()
except Exception as e:
all_hr_attendances = self.env['hr.attendance'].search([
('employee_id', 'in', self.employee_id.ids)])
error_msg.setdefault(uatt.device_id, [])
msg = str(e)
if msg not in error_msg[uatt.device_id]:
error_msg[uatt.device_id].append(msg)
if bool(error_msg):
for device, msg_list in error_msg.items():
device.message_post(body="<ol>%s</ol>" % "".join(["<li>%s</li>" % msg for msg in msg_list]))
def action_sync_attendance(self):
self._sync_attendance()
@api.model
def _prepare_unsynch_data_domain(self):
return [
('hr_attendance_id', '=', False),
('employee_id', '!=', False),
('synced', '=', False),
]
@api.model
def _cron_synch_hr_attendance(self):
unsync_data = self.env['user.attendance'].search(self._prepare_unsynch_data_domain())
unsync_data._sync_attendance()
def action_unsynic(self):
for rec in self:
rec.synced = False