@@ -80,9 +80,40 @@ class HrAttendance(models.Model):
|
||||
|
||||
def compute_attendance_ids(self):
|
||||
for att in self.env["hr.attendance"].browse(self._context.get('active_ids', self._context.get('active_id'))):
|
||||
now_time = fields.Datetime.to_datetime(att.current_data) + timedelta(hours=8, days=1)
|
||||
start_time = now_time - timedelta(days=1)
|
||||
self.env["hr.attendance.detail"].get_hr_attendance_time(att.employee_id, now_time, start_time)
|
||||
current_data = fields.Date.today() + timedelta(hours=8)
|
||||
self.env["hr.attendance.detail"].compute_attendance_detail(att.employee_id, current_data)
|
||||
|
||||
@api.constrains('check_in', 'check_out', 'employee_id')
|
||||
def _check_validity(self):
|
||||
"""重写验证方法,移除未签退记录的检查"""
|
||||
for attendance in self:
|
||||
last_attendance_before_check_in = self.env['hr.attendance'].search([
|
||||
('employee_id', '=', attendance.employee_id.id),
|
||||
('check_in', '<=', attendance.check_in),
|
||||
('id', '!=', attendance.id),
|
||||
], order='check_in desc', limit=1)
|
||||
|
||||
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:
|
||||
raise exceptions.ValidationError(_(
|
||||
"Cannot create new attendance record for %(empl_name)s, the employee was already checked in on %(datetime)s",
|
||||
empl_name=attendance.employee_id.name,
|
||||
datetime=format_datetime(self.env, attendance.check_in, dt_format=False)
|
||||
))
|
||||
if attendance.check_out:
|
||||
last_attendance_before_check_out = self.env['hr.attendance'].search([
|
||||
('employee_id', '=', attendance.employee_id.id),
|
||||
('check_in', '<', attendance.check_out),
|
||||
('id', '!=', attendance.id),
|
||||
], order='check_in desc', limit=1)
|
||||
if last_attendance_before_check_out and last_attendance_before_check_in != last_attendance_before_check_out:
|
||||
raise exceptions.ValidationError(_(
|
||||
"Cannot create new attendance record for %(empl_name)s, the employee was already checked in on %(datetime)s",
|
||||
empl_name=attendance.employee_id.name,
|
||||
datetime=format_datetime(self.env, last_attendance_before_check_out.check_in, dt_format=False)
|
||||
))
|
||||
|
||||
|
||||
|
||||
|
||||
@api.depends('current_data')
|
||||
def _compute_month_status(self):
|
||||
@@ -111,27 +142,6 @@ class HrAttendance(models.Model):
|
||||
else:
|
||||
record.company_id = False
|
||||
|
||||
def write(self, vals):
|
||||
if 'check_out' in vals:
|
||||
for record in self:
|
||||
employee_id = record.employee_id.id
|
||||
current_date = record.current_data
|
||||
local_start = datetime.combine(current_date, datetime.min.time())
|
||||
local_end = local_start + timedelta(days=1)
|
||||
utc_start = local_start - timedelta(hours=8)
|
||||
utc_end = local_end - timedelta(hours=8)
|
||||
domain = [
|
||||
("exception_type", "!=", "未打卡"),
|
||||
('employee_id', '=', employee_id),
|
||||
('checkin_time', '>=', fields.Datetime.to_string(utc_start)),
|
||||
('checkin_time', '<', fields.Datetime.to_string(utc_end))
|
||||
]
|
||||
detail_records = self.env['hr.attendance.detail'].search(domain)
|
||||
if detail_records:
|
||||
max_checkin_time = max(detail_records.mapped('checkin_time'))
|
||||
vals['check_out'] = max_checkin_time
|
||||
return super(HrAttendance, self).write(vals)
|
||||
|
||||
# @api.model
|
||||
# def web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False, lazy=True):
|
||||
# result = super().web_read_group(domain, fields, groupby, limit, offset, orderby, lazy)
|
||||
@@ -141,6 +151,7 @@ class HrAttendance(models.Model):
|
||||
# if fields_dict[field].get('type') == 'boolean':
|
||||
# group[field] = len([r for r in self.search_read(group['__domain'], fields=[field]) if r.get(field)])
|
||||
# return result
|
||||
|
||||
@api.constrains('check_in', 'check_out')
|
||||
def _check_validity_check_in_check_out(self):
|
||||
""" verifies if check_in is earlier than check_out. """
|
||||
|
||||
@@ -126,9 +126,184 @@ class HrAttendanceDetail(models.Model):
|
||||
start_time = now_time - timedelta(days=2)
|
||||
self.env["hr.attendance.detail"].get_hr_attendance_time(record.employee_id, now_time, start_time)
|
||||
|
||||
def compute_attendance_detail(self, employee, current_data):
|
||||
"""重新计算逻辑
|
||||
:param current_data: 打卡日期
|
||||
:param employee_id: 员工
|
||||
"""
|
||||
# 是否在白名单
|
||||
resource_user_ids = employee.resource_calendar_id.resource_user_ids.ids
|
||||
is_resource_user = True if employee.id in resource_user_ids else False
|
||||
|
||||
if not is_resource_user:
|
||||
template = self.env['planning.slot.template'].search([('department_id', '=', employee.department_id.id)], limit=1)
|
||||
if not template:
|
||||
template = self.env['planning.slot.template'].search([('role_id.name', '=', '集团行政')], limit=1)
|
||||
next_day = current_data + timedelta(days=1)
|
||||
start_datetime = datetime.combine(current_data, datetime.min.time()) - timedelta(hours=8)
|
||||
# 根据day2判断是否需要包含次日记录
|
||||
if template.day2 == '次日':
|
||||
end_datetime = datetime.combine(next_day, datetime.max.time()) - timedelta(hours=8)
|
||||
else:
|
||||
end_datetime = datetime.combine(current_data, datetime.max.time()) - timedelta(hours=8)
|
||||
# 筛选当前员工当天的打卡记录
|
||||
domain = [
|
||||
('employee_id', '=', employee.id),
|
||||
('checkin_time', '>=', start_datetime),
|
||||
('checkin_time', '<=', end_datetime)
|
||||
]
|
||||
attendance_records = self.search(domain)
|
||||
checkin_times = [rec.checkin_time + timedelta(hours=8) for rec in attendance_records if rec.checkin_time]
|
||||
|
||||
check_in_start = self.float_to_datetime(current_data, template.check_in_start)
|
||||
check_in_end = self.float_to_datetime(current_data, template.check_in_end)
|
||||
|
||||
# 3. 确定有效上下班时间
|
||||
# 上班时间:在上班范围内的最小打卡时间
|
||||
valid_checkins = [
|
||||
t for t in checkin_times
|
||||
if check_in_start <= t <= check_in_end
|
||||
]
|
||||
check_in_time = min(valid_checkins) - timedelta(hours=8) if valid_checkins else None
|
||||
check_out_start = self.float_to_datetime(current_data, template.check_out_start)
|
||||
if template.day2 == '次日':
|
||||
check_out_end = self.float_to_datetime(next_day, template.check_out_end)
|
||||
else:
|
||||
check_out_end = self.float_to_datetime(current_data, template.check_out_end)
|
||||
# 筛选在下班时间范围内的打卡记录
|
||||
valid_checkouts = [t for t in checkin_times if check_out_start <= t <= check_out_end]
|
||||
# 取最大的有效下班时间
|
||||
check_out_time = max(valid_checkouts) - timedelta(hours=8) if valid_checkouts else None
|
||||
attendance = self.env['hr.attendance'].search(
|
||||
[('employee_id', '=', employee.id),
|
||||
('current_data', '=', current_data),
|
||||
],
|
||||
)
|
||||
# 获取正常上下班时间
|
||||
sch_check_in_time, sch_check_out_time = self.normal_clock_in_time(template, current_data)
|
||||
is_no_card, is_early_card, is_late_card, is_abnormal, is_warning = self.state_judgment(check_in_time, check_out_time, sch_check_in_time, sch_check_out_time, current_data)
|
||||
if attendance:
|
||||
attendance.write({
|
||||
'check_in': check_in_time,
|
||||
'check_out': check_out_time,
|
||||
'is_no_card': is_no_card,
|
||||
'is_early_card': is_early_card,
|
||||
'is_late_card': is_late_card,
|
||||
'is_abnormal': is_abnormal,
|
||||
'is_warning': is_warning,
|
||||
})
|
||||
else:
|
||||
self.env['hr.attendance'].create({
|
||||
'employee_id': employee.id,
|
||||
'current_data': current_data,
|
||||
'check_in': check_in_time,
|
||||
'check_out': check_out_time,
|
||||
'sch_check_in_time': sch_check_in_time,
|
||||
'sch_check_out_time': sch_check_out_time,
|
||||
'is_no_card': is_no_card,
|
||||
'is_early_card': is_early_card,
|
||||
'is_late_card': is_late_card,
|
||||
'is_abnormal': is_abnormal,
|
||||
'is_warning': is_warning,
|
||||
})
|
||||
|
||||
def normal_clock_in_time(self, template, current_data):
|
||||
"""
|
||||
:param template: 班次模板
|
||||
:param current_data: 打卡日期
|
||||
:return: 正常开始上班时间和下班时间
|
||||
"""
|
||||
check_in_start = template.start_time
|
||||
check_out_end = template.end_time
|
||||
date = current_data
|
||||
start_hour = int(check_in_start)
|
||||
start_minute = int((check_in_start - start_hour) * 60)
|
||||
end_hour = int(check_out_end)
|
||||
end_minute = int((check_out_end - end_hour) * 60)
|
||||
start_datetime = datetime.combine(date, datetime.min.time()).replace(
|
||||
hour=start_hour, minute=start_minute, second=0, microsecond=0
|
||||
)
|
||||
end_datetime = datetime.combine(date, datetime.min.time()).replace(
|
||||
hour=end_hour, minute=end_minute, second=0, microsecond=0
|
||||
)
|
||||
return start_datetime, end_datetime
|
||||
|
||||
def float_to_datetime(self, base_date, hour_float):
|
||||
"""
|
||||
将小时浮点数(如9.5表示9:30)转换为具体datetime
|
||||
:param base_date: 基准日期(date对象)
|
||||
:param hour_float: 小时浮点数
|
||||
:return: datetime对象
|
||||
"""
|
||||
hours = int(hour_float)
|
||||
minutes = int((hour_float - hours) * 60)
|
||||
return datetime.combine(base_date, datetime.min.time()) + timedelta(hours=hours, minutes=minutes)
|
||||
|
||||
def state_judgment(self, check_in, check_out, start_datetime, end_datetime, current_data):
|
||||
"""
|
||||
判断打卡状态
|
||||
:param check_in: 签到时间
|
||||
:param check_out: 签出时间
|
||||
:param start_datetime: 标准上班打卡时间
|
||||
:param end_datetime: 标准下班打卡时间
|
||||
:param current_data: 打卡日期
|
||||
:return:
|
||||
"""
|
||||
is_no_card = False
|
||||
is_early_card = False
|
||||
is_late_card = False
|
||||
is_abnormal = False
|
||||
is_warning = False
|
||||
# 判断是否工作日
|
||||
if self._process_shift_days(current_data):
|
||||
if not check_in or not check_out:
|
||||
is_no_card = True
|
||||
if check_in and start_datetime and check_in > start_datetime:
|
||||
is_late_card = True
|
||||
if check_out and end_datetime and check_out < end_datetime:
|
||||
is_early_card = True
|
||||
if is_no_card or is_early_card or is_late_card:
|
||||
is_abnormal = True
|
||||
is_warning = True
|
||||
return is_no_card, is_early_card, is_late_card, is_abnormal, is_warning
|
||||
|
||||
def _process_shift_days(self, current_data):
|
||||
"""
|
||||
判断是否是工作日 调休日 双休
|
||||
"""
|
||||
calendar_leaves = self.env['resource.calendar.leaves'].search([("calendar_id", "=", "行政考勤工作日")])
|
||||
holidays_list = []
|
||||
shift_days_list = []
|
||||
# 提取节假日和调休日
|
||||
for leave in calendar_leaves:
|
||||
if leave.holidays:
|
||||
holidays_list.extend(leave.holidays.split(','))
|
||||
if leave.alternate_rest_day:
|
||||
holidays_list.extend(leave.alternate_rest_day.split(','))
|
||||
if leave.shift_replacement_day:
|
||||
shift_days_list.extend(leave.shift_replacement_day.split(','))
|
||||
|
||||
holidays_list = [self.remove_leading_zero(date) for date in holidays_list]
|
||||
shift_days_list = [self.remove_leading_zero(date) for date in shift_days_list]
|
||||
|
||||
if (current_data in holidays_list) or (current_data.weekday() in [5, 6]):
|
||||
return False
|
||||
elif current_data in shift_days_list:
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
def remove_leading_zero(self, date_str):
|
||||
month, day = date_str.split('月')
|
||||
month = month.lstrip('0')
|
||||
day = day.rstrip('日')
|
||||
day = day.lstrip('0')
|
||||
return f'{month}月{day}日'
|
||||
|
||||
|
||||
# 传参按一个人打卡一次传参
|
||||
|
||||
|
||||
# 传参按一个人打卡一, shift_days_list次传参
|
||||
# @api.model_create_multi
|
||||
# def create(self, vals_list):
|
||||
# res = super(HrAttendanceDetail, self).create(vals_list)
|
||||
|
||||
Reference in New Issue
Block a user