1696 lines
85 KiB
Python
1696 lines
85 KiB
Python
# -*- coding: utf-8 -*-
|
||
import os
|
||
import base64
|
||
import pytz
|
||
import calendar
|
||
from datetime import datetime, timedelta, time
|
||
import logging
|
||
import traceback
|
||
from odoo import api, fields, models, tools, _
|
||
from dateutil.relativedelta import relativedelta
|
||
from datetime import date
|
||
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
|
||
class HrAttendanceDetailImage(models.Model):
|
||
_name = "hr.attendance.detail.image"
|
||
_description = "打卡原始抓取数据图片"
|
||
|
||
attendance_detail_id = fields.Many2one('hr.attendance.detail', string="原始打卡记录", ondelete="cascade",
|
||
readonly=True)
|
||
media_url = fields.Char(string='附件地址')
|
||
media_file = fields.Binary(string='附件文件', compute="_compute_media_file")
|
||
|
||
@api.depends('media_url')
|
||
def _compute_media_file(self):
|
||
for record in self:
|
||
record.media_file = None
|
||
wx_work_auth = self.env['auth.oauth.provider'].search([('name', '=', 'Wechat Card')], limit=1)
|
||
if record.media_url:
|
||
image_path = os.path.join(wx_work_auth.wx_work_image_path, record.media_url)
|
||
record.media_file = base64.b64encode(open(image_path, 'rb').read()) if image_path else False
|
||
|
||
|
||
class HrAttendanceDetail(models.Model):
|
||
_name = "hr.attendance.detail"
|
||
_description = "打卡原始抓取数据"
|
||
_order = 'checkin_time desc'
|
||
|
||
name = fields.Char(related='employee_id.name')
|
||
employee_id = fields.Many2one('hr.employee', string="员工", readonly=True)
|
||
company_id = fields.Many2one('res.company', string="公司", related="employee_id.company_id", store=True)
|
||
department_id = fields.Many2one('hr.department', string="部门", related="employee_id.department_id", readonly=True, store=True)
|
||
source = fields.Selection([('iot', '考勤机'), ('mini_programs', '小程序')], string="来源")
|
||
attendance_iot_id = fields.Many2one('attendance.iot', string="设备号")
|
||
attendance_address = fields.Char(related="attendance_iot_id.attendance_address", string="设备地址")
|
||
userid = fields.Char(string='userid')
|
||
groupname = fields.Char(string='打卡规则名称')
|
||
checkin_type = fields.Char(string='打卡类型')
|
||
exception_type = fields.Char(string='异常类型')
|
||
checkin_time = fields.Datetime(string='打卡时间')
|
||
location_title = fields.Char(string='打卡地点')
|
||
location_detail = fields.Char(string='打卡地点详情')
|
||
wifiname = fields.Char(string='打卡wifi名称')
|
||
notes = fields.Char(string='打卡备注')
|
||
wifimac = fields.Char(string='打卡的MAC地址')
|
||
mediaids = fields.Char(string='打卡的附件media_id')
|
||
lat = fields.Integer(string='位置打卡地点纬度')
|
||
lng = fields.Integer(string='位置打卡地点经度')
|
||
deviceid = fields.Char(string='打卡设备id')
|
||
sch_checkin_time = fields.Datetime(string='标准打卡时间')
|
||
groupid = fields.Integer(string='规则id')
|
||
schedule_id = fields.Char(string='班次id')
|
||
timeline_id = fields.Integer(string='时段id')
|
||
remark = fields.Char(string="备注")
|
||
|
||
attendance_detail_image_ids = fields.One2many(string="打卡记录图片", comodel_name='hr.attendance.detail.image',
|
||
inverse_name='attendance_detail_id')
|
||
hr_attendance_id = fields.Many2one('hr.attendance', string="出勤记录", ondelete="cascade", readonly=True)
|
||
seconds = fields.Integer(
|
||
string='秒',
|
||
compute='_compute_seconds',
|
||
)
|
||
@api.depends('checkin_time')
|
||
def _compute_seconds(self):
|
||
for record in self:
|
||
if not record.checkin_time:
|
||
record.seconds = 0
|
||
continue
|
||
record.seconds = record.checkin_time.second
|
||
|
||
def action_show_details(self):
|
||
self.ensure_one()
|
||
view = self.env.ref("yuthon_wechat.view_hr_attendance_detail_image_tree")
|
||
return {
|
||
"name": _("图片详情"),
|
||
"type": "ir.actions.act_window",
|
||
"view_mode": "form",
|
||
"res_model": "hr.attendance.detail.image",
|
||
"views": [(view.id, "tree")],
|
||
"view_id": view.id,
|
||
"target": "new",
|
||
"res_id": self.id,
|
||
"domain": [('attendance_detail_id', '=', self.id)],
|
||
"context": {'default_attendance_detail_id': self.id}
|
||
}
|
||
|
||
@api.model
|
||
def create(self, vals):
|
||
record = super(HrAttendanceDetail, self).create(vals)
|
||
if record.employee_id.name not in ['Administrator']:
|
||
resource_user_ids = record.employee_id.resource_calendar_id.resource_user_ids.ids
|
||
is_resource_user = True if record.employee_id.id in resource_user_ids else False
|
||
employee_id = record.employee_id
|
||
checkin_datetime = record.checkin_time
|
||
last_record = self.search([
|
||
('employee_id', '=', employee_id.id),
|
||
('source', '=', 'iot'),
|
||
('checkin_time', '<', checkin_datetime)
|
||
], order='checkin_time desc', limit=1)
|
||
# 如果上一条记录存在,并且时间差 < 60秒 → 不发提醒
|
||
skip_notify = False
|
||
if last_record:
|
||
time_diff = checkin_datetime - last_record.checkin_time
|
||
if time_diff.total_seconds() < 60:
|
||
skip_notify = True # 小于1分钟,跳过提醒
|
||
if record.employee_id and record.checkin_time and record.source == 'iot' and not is_resource_user and not skip_notify:
|
||
current_data = record.checkin_time.date() + timedelta(hours=8)
|
||
day_start = checkin_datetime.replace(hour=0, minute=0, second=0, microsecond=0)
|
||
day_end = checkin_datetime.replace(hour=23, minute=59, second=59, microsecond=99)
|
||
daily_iot_records = self.search([
|
||
('employee_id', '=', employee_id.id),
|
||
('source', '=', 'iot'),
|
||
('checkin_time', '>=', day_start),
|
||
('checkin_time', '<=', day_end)
|
||
], order='checkin_time asc')
|
||
if daily_iot_records and daily_iot_records[0].id == record.id:
|
||
message = f"打卡成功!今日上班时间:{record.checkin_time + timedelta(hours=8)}: {employee_id.name},祝您工作愉快!"
|
||
self.sudo().env["wecom.apps"].sync_send_message(
|
||
category='att', user_id=employee_id.wecom_userid,
|
||
message=message,
|
||
)
|
||
if record.employee_id.name == '蒋彬':
|
||
self.sudo().env["wecom.apps"].sync_send_message(
|
||
category='att', user_id='18562027762',
|
||
message=message + f'管理员测试管理员测试{vals}',
|
||
)
|
||
self.write({'remark': message})
|
||
_logger.info(message)
|
||
else:
|
||
resource_user_ids = employee_id.resource_calendar_id.resource_user_ids.ids
|
||
is_resource_user = True if employee_id.id in resource_user_ids else False
|
||
# 哺乳人员
|
||
suckling_employees = employee_id.resource_calendar_id.breastfeeding_holiday_ids.mapped('employee_id')
|
||
def get_all_upper_depts(dept):
|
||
"""递归获取部门及其所有上级部门的ID列表"""
|
||
dept_ids = [dept.id]
|
||
if dept.parent_id:
|
||
dept_ids.extend(get_all_upper_depts(dept.parent_id))
|
||
return dept_ids
|
||
|
||
all_dept_ids = get_all_upper_depts(employee_id.department_id)
|
||
|
||
if not is_resource_user:
|
||
slot_start = datetime.combine(current_data, datetime.min.time()) - timedelta(hours=8)
|
||
slot_end = datetime.combine(current_data + timedelta(days=1), datetime.min.time()) - timedelta(
|
||
hours=8)
|
||
# 查找员工对应的班次
|
||
planning_slots = self.env['planning.slot'].search([
|
||
('resource_id', '=', employee_id.resource_id.id),
|
||
('start_datetime', '>=', slot_start),
|
||
('start_datetime', '<', slot_end),
|
||
], limit=1)
|
||
planning_name = planning_slots.planning_name if planning_slots else None
|
||
template_domain = []
|
||
if planning_name:
|
||
template_domain.append(('planning_name', '=', planning_name))
|
||
template_domain.append(('department_id', 'in', all_dept_ids))
|
||
template = self.env['planning.slot.template'].search(template_domain, limit=1)
|
||
# 如果模板里面找不到这个人 则还是往下判断(模板为空)
|
||
if employee_id.id not in template.role_id.resource_ids.employee_id.ids:
|
||
template = None
|
||
if not template:
|
||
role_templates = [
|
||
{'role_name': '厨卫(集团)', 'template': None},
|
||
{'role_name': '项目运营岗', 'template': None}
|
||
]
|
||
|
||
for item in role_templates:
|
||
item['template'] = self.env['planning.slot.template'].search(
|
||
[('role_id.name', '=', item['role_name'])], )
|
||
for item in role_templates:
|
||
if item['template']:
|
||
employee_ids = item['template'].role_id.resource_ids.employee_id.ids or []
|
||
employee_list = list(employee_ids)
|
||
if employee_id.id in employee_list:
|
||
template = item['template']
|
||
break
|
||
if not template:
|
||
template = self.env['planning.slot.template'].search(
|
||
[('role_id.name', '=', '集团行政')],
|
||
limit=1
|
||
)
|
||
sch_check_in_time, sch_check_out_time = self.normal_clock_in_time(template, current_data)
|
||
# 判断是否是哺乳人员
|
||
if employee_id in suckling_employees:
|
||
# 判断是否是哺乳人员并调整标准打卡时间
|
||
current_date = fields.Date.to_date(current_data) if current_data else None
|
||
if current_date:
|
||
# 查找当天的哺乳假记录(匹配员工+日期范围)
|
||
breastfeeding_record = self.env['breastfeeding.holiday'].search([
|
||
('employee_id', '=', employee_id.id),
|
||
('calendar_start_time', '<=', current_date),
|
||
('calendar_end_time', '>=', current_date),
|
||
], limit=1)
|
||
if breastfeeding_record.time_slot_selection == 'afternoon':
|
||
sch_check_out_time = sch_check_out_time - timedelta(hours=1)
|
||
|
||
if checkin_datetime > sch_check_out_time:
|
||
message = f"打卡成功!今日下班时间:{record.checkin_time + timedelta(hours=8)}: {employee_id.name},祝您归途平安!"
|
||
self.sudo().env["wecom.apps"].sync_send_message(
|
||
category='att', user_id=employee_id.wecom_userid,
|
||
message=message,
|
||
)
|
||
self.write({'remark': message})
|
||
_logger.info(message)
|
||
|
||
if record.employee_id and record.checkin_time:
|
||
self._sync_to_hr_attendance(record)
|
||
_logger.info(f"{record.employee_id.name}--------")
|
||
return record
|
||
|
||
def _sync_to_hr_attendance(self, record):
|
||
"""同步打卡记录到hr.attendance 更新上下班打卡时间 """
|
||
current_date = record.checkin_time.date() + timedelta(hours=8)
|
||
self.env["hr.attendance.detail"].compute_attendance_detail(record.employee_id, current_date)
|
||
|
||
def compute_attendance_detail(self, employee, current_data, is_manual= False):
|
||
"""重新计算逻辑
|
||
:param current_data: 打卡日期
|
||
:param employee_id: 员工
|
||
:param is_manual: 是否是手动计算
|
||
"""
|
||
# 是否在白名单
|
||
resource_user_ids = employee.resource_calendar_id.resource_user_ids.ids
|
||
is_resource_user = True if employee.id in resource_user_ids else False
|
||
# 哺乳人员
|
||
suckling_employees = employee.resource_calendar_id.breastfeeding_holiday_ids.mapped('employee_id')
|
||
def get_all_upper_depts(dept):
|
||
"""递归获取部门及其所有上级部门的ID列表"""
|
||
dept_ids = [dept.id]
|
||
if dept.parent_id:
|
||
dept_ids.extend(get_all_upper_depts(dept.parent_id))
|
||
return dept_ids
|
||
all_dept_ids = get_all_upper_depts(employee.department_id)
|
||
|
||
if is_resource_user:
|
||
attendance = self.env['hr.attendance'].search(
|
||
[('employee_id', '=', employee.id),
|
||
('current_data', '=', current_data),
|
||
],
|
||
)
|
||
if attendance:
|
||
attendance.write({
|
||
'is_early_card': False,
|
||
'is_late_card': False,
|
||
'is_abnormal': False,
|
||
'is_no_card': False,
|
||
'absenteeism_card': False,
|
||
'is_warning': False,
|
||
})
|
||
|
||
if not is_resource_user:
|
||
slot_start = datetime.combine(current_data, datetime.min.time()) - timedelta(hours=8)
|
||
slot_end = datetime.combine(current_data + timedelta(days=1), datetime.min.time()) - timedelta(hours=8)
|
||
# 查找员工对应的班次
|
||
planning_slots = self.env['planning.slot'].search([
|
||
('resource_id', '=', employee.resource_id.id),
|
||
('start_datetime', '>=', slot_start),
|
||
('start_datetime', '<', slot_end),
|
||
], limit=1)
|
||
planning_name = planning_slots.planning_name if planning_slots else None
|
||
template_domain = []
|
||
if planning_name:
|
||
template_domain.append(('planning_name', '=', planning_name))
|
||
template_domain.append(('department_id', 'in', all_dept_ids))
|
||
template = self.env['planning.slot.template'].search(template_domain, limit=1)
|
||
# 如果模板里面找不到这个人 则还是往下判断(模板为空)
|
||
if employee.id not in template.role_id.resource_ids.employee_id.ids:
|
||
template = None
|
||
|
||
if not template:
|
||
role_templates = [
|
||
{'role_name': '厨卫(集团)', 'template': None},
|
||
{'role_name': '项目运营岗', 'template': None}
|
||
]
|
||
|
||
for item in role_templates:
|
||
item['template'] = self.env['planning.slot.template'].search([('role_id.name', '=', item['role_name'])],)
|
||
|
||
for item in role_templates:
|
||
if item['template']:
|
||
employee_ids = item['template'].role_id.resource_ids.employee_id.ids or []
|
||
employee_list = list(employee_ids)
|
||
if employee.id in employee_list:
|
||
template = item['template']
|
||
break
|
||
|
||
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)
|
||
# 判断是否需要包含次日记录
|
||
end_datetime = datetime.combine(current_data, datetime.max.time()) - timedelta(hours=8)
|
||
if template.check_out_start == 0.0:
|
||
template.check_out_start = 24.0
|
||
if template.check_out_end < template.check_out_start:
|
||
end_datetime = datetime.combine(next_day, 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)
|
||
if template.check_in_end < template.check_in_start:
|
||
check_in_end = self.float_to_datetime(next_day, template.check_in_end)
|
||
valid_checkins = [
|
||
t for t in checkin_times
|
||
if check_in_start <= t <= check_in_end
|
||
]
|
||
# 3. 确定有效上下班时间
|
||
# 上班时间:在上班范围内的最小打卡时间
|
||
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)
|
||
check_out_end = self.float_to_datetime(current_data, template.check_out_end)
|
||
if template.check_out_start == 0.0:
|
||
template.check_out_start = 24.0
|
||
if template.check_out_end < template.check_out_start:
|
||
check_out_end = self.float_to_datetime(next_day, 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),
|
||
],
|
||
)
|
||
if check_in_time and check_out_time and check_in_time == check_out_time:
|
||
check_out_time = None
|
||
|
||
# 获取正常上下班时间
|
||
sch_check_in_time, sch_check_out_time = self.normal_clock_in_time(template, current_data)
|
||
|
||
# 判断是否是哺乳人员
|
||
if employee in suckling_employees:
|
||
# 判断是否是哺乳人员并调整标准打卡时间
|
||
current_date = fields.Date.to_date(current_data) if current_data else None
|
||
if current_date:
|
||
# 查找当天的哺乳假记录(匹配员工+日期范围)
|
||
breastfeeding_record = self.env['breastfeeding.holiday'].search([
|
||
('employee_id', '=', employee.id),
|
||
('calendar_start_time', '<=', current_date),
|
||
('calendar_end_time', '>=', current_date),
|
||
], limit=1)
|
||
if breastfeeding_record:
|
||
if breastfeeding_record.time_slot_selection == 'morning':
|
||
sch_check_in_time = sch_check_in_time + timedelta(hours=1)
|
||
else:
|
||
sch_check_out_time = sch_check_out_time - timedelta(hours=1)
|
||
|
||
is_early_card, is_late_card, is_abnormal, is_warning, absenteeism_card, is_no_card = self.state_judgment(employee, check_in_time, check_out_time, sch_check_in_time, sch_check_out_time, current_data, is_manual, attendance)
|
||
is_expatriation = self.env['expatriation.approval'].search([('work_end', '=', True),('expatriates_ids', 'in', [employee.id]), ('is_effect', '=', False)])
|
||
if not self._process_shift_days(current_data) and template.role_id.name == '集团行政':
|
||
start_datetime = datetime.combine(current_data, datetime.min.time())
|
||
end_datetime = datetime.combine(current_data, datetime.max.time())
|
||
domain = [
|
||
('employee_id', '=', employee.id),
|
||
('checkin_time', '>=', start_datetime),
|
||
('checkin_time', '<=', end_datetime)
|
||
]
|
||
attendance_records = self.search(domain)
|
||
if attendance_records:
|
||
checkin_time_list = [rec.checkin_time for rec in attendance_records if rec.checkin_time]
|
||
if checkin_time_list:
|
||
min_time = min(checkin_time_list)
|
||
max_time = max(checkin_time_list)
|
||
if min_time == max_time:
|
||
check_in_time = min_time
|
||
check_out_time = None
|
||
else:
|
||
check_in_time = min_time
|
||
check_out_time = max_time
|
||
|
||
if attendance:
|
||
attendance.write({
|
||
'check_in': check_in_time,
|
||
'check_out': check_out_time,
|
||
'shift_id': planning_slots.id,
|
||
'shift_name': planning_slots.display_name,
|
||
'is_early_card': is_early_card,
|
||
'is_late_card': is_late_card,
|
||
'is_abnormal': is_abnormal,
|
||
'is_warning': is_warning,
|
||
'is_check_in_red': False,
|
||
'is_check_out_red': False,
|
||
'is_approval': False,
|
||
'is_no_card': is_no_card,
|
||
'is_holiday': False,
|
||
'is_week': False,
|
||
'absenteeism_card': absenteeism_card,
|
||
'is_expatriation': is_expatriation,
|
||
})
|
||
else:
|
||
attendance = self.env['hr.attendance'].create({
|
||
'employee_id': employee.id,
|
||
'shift_id': planning_slots.id,
|
||
'shift_name': planning_slots.display_name,
|
||
'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_early_card': is_early_card,
|
||
'is_late_card': is_late_card,
|
||
'is_abnormal': is_abnormal,
|
||
'is_no_card': is_no_card,
|
||
'is_warning': is_warning,
|
||
'is_check_in_red': False,
|
||
'is_check_out_red': False,
|
||
'is_approval': False,
|
||
'is_expatriation': is_expatriation,
|
||
'is_holiday': False,
|
||
'is_week': False,
|
||
'absenteeism_card': absenteeism_card,
|
||
})
|
||
attendance.update_attendance(employee, current_data)
|
||
else:
|
||
att_id = self.env['hr.attendance'].search([('employee_id', '=', employee.id), ('current_data', '=', current_data)])
|
||
if att_id:
|
||
att_id.write({
|
||
'is_no_card': False,
|
||
'is_early_card': False,
|
||
'is_late_card': False,
|
||
'is_abnormal': False,
|
||
'is_warning': False,
|
||
'absenteeism_card': False,
|
||
})
|
||
|
||
def normal_clock_in_time(self, template, current_data):
|
||
"""
|
||
:param template: 班次模板
|
||
:param current_data: 打卡日期
|
||
:return: 正常开始上班时间和下班时间
|
||
"""
|
||
if template.day2 == '次日':
|
||
check_out_end = template.end_time2
|
||
else:
|
||
check_out_end = template.end_time
|
||
|
||
if template.planning_name == '集团行政':
|
||
check_out_end = template.end_time
|
||
|
||
check_in_start = template.start_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
|
||
) - timedelta(hours=8)
|
||
if template.planning_name == '厨卫(集团本部)':
|
||
start_datetime = datetime.combine(date, datetime.min.time()).replace(
|
||
hour=start_hour, minute=start_minute, second=0, microsecond=0
|
||
) - timedelta(hours=6)
|
||
if template.day2 == '次日':
|
||
end_datetime = datetime.combine(date, datetime.min.time()).replace(
|
||
hour=end_hour, minute=end_minute, second=0, microsecond=0
|
||
) - timedelta(hours=8) + timedelta(days=1)
|
||
else:
|
||
end_datetime = datetime.combine(date, datetime.min.time()).replace(
|
||
hour=end_hour, minute=end_minute, second=0, microsecond=0
|
||
) - timedelta(hours=8)
|
||
if template.planning_name == '中夜':
|
||
end_datetime = datetime.combine(date, datetime.min.time()).replace(
|
||
hour=22, minute=0, second=0, microsecond=0
|
||
) - timedelta(hours=8)
|
||
|
||
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, employee, check_in, check_out, start_datetime, end_datetime, current_data, is_manual, attendance):
|
||
"""
|
||
判断打卡状态
|
||
:param check_in: 签到时间
|
||
:param check_out: 签出时间
|
||
:param start_datetime: 标准上班打卡时间
|
||
:param end_datetime: 标准下班打卡时间
|
||
:param current_data: 打卡日期
|
||
:param is_manual: 是否为手工补录
|
||
:return: is_early_card, is_late_card, is_abnormal, is_warning, absenteeism_card
|
||
"""
|
||
is_early_card = False
|
||
is_late_card = False
|
||
is_abnormal = False
|
||
is_warning = False
|
||
absenteeism_card = False
|
||
is_no_card = False
|
||
|
||
# 判断是否为外派中
|
||
expat_approval = self.env['expatriation.approval'].search([
|
||
('is_effect', '=', False),
|
||
('work_end', '=', True),
|
||
('expatriates_ids', 'in', [employee.id])
|
||
], limit=1)
|
||
|
||
# 查找有效的返回审批单
|
||
back_approval = self.env['back.approval'].search([
|
||
('is_void', '=', False),
|
||
('work_end', '=', True),
|
||
('expatriates_id', '=', employee.id)
|
||
], limit=1)
|
||
|
||
# 3. 外派中:当前时间 >= 外派开始日期 → 所有状态为False
|
||
if expat_approval and expat_approval.start_date:
|
||
if current_data >= expat_approval.start_date:
|
||
return is_early_card, is_late_card, is_abnormal, is_warning, absenteeism_card, is_no_card
|
||
|
||
# 4. 返回期间:打卡日期在返回的开始-结束日期之间 → 所有状态为False
|
||
if back_approval and back_approval.start_date and back_approval.end_date:
|
||
if back_approval.start_date <= current_data <= back_approval.end_date:
|
||
return is_early_card, is_late_card, is_abnormal, is_warning, absenteeism_card, is_no_card
|
||
|
||
def remove_seconds(dt):
|
||
"""清除datetime对象的秒数和微秒"""
|
||
if dt:
|
||
return dt.replace(second=0, microsecond=0)
|
||
return dt
|
||
|
||
# 清除所有时间的秒数
|
||
check_in = remove_seconds(check_in)
|
||
check_out = remove_seconds(check_out)
|
||
start_datetime = remove_seconds(start_datetime)
|
||
end_datetime = remove_seconds(end_datetime)
|
||
|
||
if employee.department_id and employee.department_id.name == '厨卫':
|
||
start_datetime = start_datetime.replace(hour=1, minute=0, second=0, microsecond=0)
|
||
|
||
# 判断是否是行政班次
|
||
employee_list = []
|
||
role_ids = self.env['planning.role'].search([('name', 'not in', ['厨卫(集团)', '项目运营岗'])])
|
||
for role in role_ids:
|
||
for resource_id in role.resource_ids:
|
||
employee_list.append(resource_id.employee_id.id)
|
||
if employee.id not in employee_list:
|
||
# 判断是否工作日
|
||
if not self._process_shift_days(current_data):
|
||
return is_early_card, is_late_card, is_abnormal, is_warning, absenteeism_card, is_no_card
|
||
|
||
is_tianhe_property = employee.company_id.name == "天河物管"
|
||
|
||
# 只要是旷工 就不算早退 或者迟到
|
||
# 迟到判断
|
||
if check_in and start_datetime and check_in > start_datetime:
|
||
late_delta = (check_in - start_datetime).total_seconds() / 60
|
||
if is_tianhe_property:
|
||
# 天河物管:2分钟包容时间
|
||
if employee.late_allowed == 0:
|
||
if late_delta > 2:
|
||
is_late_card = True
|
||
else:
|
||
if attendance.is_late_allowed == False:
|
||
# 迟到超过10分钟 并且也算迟扣允许迟到次数 到
|
||
employee.late_allowed -= 1
|
||
attendance.is_late_allowed = True
|
||
if late_delta > 10:
|
||
is_late_card = True
|
||
|
||
# 迟到超过60分钟标记为旷工
|
||
if late_delta > 60:
|
||
absenteeism_card = True
|
||
is_late_card = False
|
||
else:
|
||
if check_in > start_datetime:
|
||
is_late_card = True
|
||
# 迟到超过60分钟标记为旷工
|
||
if late_delta > 60:
|
||
absenteeism_card = True
|
||
is_late_card = False
|
||
if not check_out:
|
||
is_no_card = True
|
||
absenteeism_card = True
|
||
is_abnormal = True
|
||
# 早退判断
|
||
if check_out and end_datetime:
|
||
early_delta = (end_datetime - check_out).total_seconds() / 60
|
||
if is_tianhe_property:
|
||
# 天河物管:超过2分钟才算早退
|
||
if early_delta > 2:
|
||
is_early_card = True
|
||
if early_delta > 60:
|
||
absenteeism_card = True
|
||
is_early_card = False
|
||
else:
|
||
if check_out < end_datetime:
|
||
is_early_card = True
|
||
if early_delta > 60:
|
||
absenteeism_card = True
|
||
is_early_card = False
|
||
|
||
# 异常和警告判断
|
||
if is_early_card or is_late_card or absenteeism_card:
|
||
is_abnormal = True
|
||
is_warning = True
|
||
|
||
if is_manual == True:
|
||
if not check_out or not check_in:
|
||
is_no_card = True
|
||
absenteeism_card = True
|
||
is_warning = True
|
||
is_abnormal = True
|
||
|
||
return is_early_card, is_late_card, is_abnormal, is_warning, absenteeism_card, is_no_card
|
||
|
||
def _process_shift_days(self, current_data):
|
||
"""
|
||
判断是否是工作日 调休日 双休
|
||
"""
|
||
current_year = (fields.Date.today() + timedelta(hours=8)).year
|
||
query_start = datetime(current_year, 1, 1, 0, 0, 0) - timedelta(hours=8)
|
||
query_end = datetime(current_year, 12, 31, 23, 59, 59) - timedelta(hours=8)
|
||
calendar_leaves = self.env['resource.calendar.leaves'].sudo().search([
|
||
("calendar_id.name", "=", "行政考勤工作日"),
|
||
("date_from", ">=", query_start),
|
||
("date_from", "<=", query_end)
|
||
])
|
||
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]
|
||
result = f"{current_data.month}月{current_data.day}日"
|
||
if result in shift_days_list:
|
||
return True
|
||
elif (result in holidays_list) or (current_data.weekday() in [5, 6]):
|
||
return False
|
||
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}日'
|
||
|
||
def _get_wx_attendance_user_list(self):
|
||
"""
|
||
获取企业微信的关联用户
|
||
:return: 用户列表
|
||
"""
|
||
user_obj = self.sudo().env['res.users'].search([('employee_id.id', '!=', False)])
|
||
total_users = len(user_obj)
|
||
remaining_users = total_users
|
||
user_list = []
|
||
for user in user_obj:
|
||
if user.oauth_uid:
|
||
_logger.info("{} id:{},总:{},剩:{},已:{},{}".format(user.name, user.id, total_users, remaining_users,
|
||
total_users - remaining_users, user.company_id.name))
|
||
remaining_users -= 1
|
||
user_list.append(user.oauth_uid)
|
||
return user_list
|
||
|
||
@staticmethod
|
||
def _get_wx_attendance_data(userid_limit, now_time, start_time):
|
||
"""
|
||
获取企业微信的打卡信息
|
||
:param wx_work_checkin_api: 企业微信api
|
||
:param userid_limit: 用户列表
|
||
:return:
|
||
"""
|
||
start_time_stamp = int(start_time.timestamp())
|
||
end_time_stamp = int(now_time.timestamp())
|
||
# 整理请求信息
|
||
data = {
|
||
"opencheckindatatype": 3,
|
||
"starttime": start_time_stamp,
|
||
"endtime": end_time_stamp,
|
||
"need_photo": 1,
|
||
"useridlist": userid_limit
|
||
}
|
||
# 重写一个类处理result获取想要的结果
|
||
|
||
return data
|
||
|
||
@staticmethod
|
||
def _check_wx_attendance_data(response, userid_limit):
|
||
"""
|
||
检查请求返回的信息是否正确
|
||
:param response: 请求返回信息
|
||
:param userid_limit: 用户列表
|
||
:return: 检查结果
|
||
"""
|
||
# 对返回信息进行整理
|
||
if response.status_code != 200:
|
||
_logger.error('抓取出勤数据请求失败\n异常:{0}\n用户列表{1}'.format(str(response.json()), str(userid_limit)))
|
||
return []
|
||
result = response.json()
|
||
if result.get('errcode') != 0:
|
||
# 301021至少有一个不存在通讯录
|
||
# 301024获取打卡间隔受限
|
||
_logger.error('获取外出打卡数据失败\n异常:{0}\n数量:{1}\n用户列表:{2}'.format(str(result.get('errmsg')), len(userid_limit), str(userid_limit)))
|
||
return []
|
||
checkin_data = result.get('checkindata', [])
|
||
if not checkin_data:
|
||
_logger.error('没有找到外出打卡记录\n异常:{0}\n用户列表{1}'.format(str(response.json()), str(userid_limit)))
|
||
return []
|
||
return checkin_data
|
||
|
||
def _check_wx_attendance_time(self, data, userid_limit):
|
||
"""
|
||
检查保存之前的数据是否准确,并且分析出想要的打卡时间
|
||
:param data: 原始数据
|
||
:param userid_limit: 用户列表
|
||
:return: 分析后的打卡数据
|
||
"""
|
||
userid = data.get("userid", "")
|
||
checkin_time_stamp = data.get("checkin_time", "")
|
||
sch_checkin_time_stamp = data.get("sch_checkin_time", None)
|
||
if not userid or not checkin_time_stamp:
|
||
_logger.error('没有抓取到用户出勤信息,异常:{0},用户列表{1}'.format(str(data), str(userid_limit)))
|
||
return False
|
||
checkin_time = datetime.fromtimestamp(checkin_time_stamp) if checkin_time_stamp else None
|
||
sch_checkin_time = datetime.fromtimestamp(sch_checkin_time_stamp) if sch_checkin_time_stamp else None
|
||
return checkin_time, sch_checkin_time
|
||
|
||
def _check_wx_attendance_media(self, data):
|
||
"""
|
||
分析返回来的数据获取图片原图
|
||
:param data: 原始数据
|
||
:param userid_limit: 用户列表
|
||
:return: 获取后的图片
|
||
"""
|
||
media_list = []
|
||
userid = data.get("userid", "")
|
||
mediaids_list = data.get("mediaids", [])
|
||
if not mediaids_list:
|
||
return media_list
|
||
|
||
for media_id in mediaids_list:
|
||
data = {"media_id": media_id}
|
||
try:
|
||
result, response = self.sudo().env["wecom.apps"].sync_media_get(data)
|
||
if result:
|
||
filename = response.headers['Content-Disposition'].split('; ')[2].replace('filename=', '').replace('"', '')
|
||
wx_work_auth = self.env['auth.oauth.provider'].search([('name', '=', 'Wechat Card')], limit=1)
|
||
wx_work_image = os.path.join(wx_work_auth.wx_work_image_path, str(filename))
|
||
with open(wx_work_image, 'wb') as f:f.write(response.content)
|
||
media_list.append((0, 0, {'media_url': filename,}))
|
||
else:
|
||
_logger.error('检查保存之前的数据分析出想要的数据失败,异常:{0},数据信息{1}'.format(str(response), str(data)))
|
||
except Exception as e:
|
||
_logger.error('请求原始图片失败,异常:{0},数据信息{1},异常详情:{2}'.format(str(e), str(data),str(traceback.format_exc())))
|
||
|
||
return media_list
|
||
|
||
def _save_wx_attendance_detail_data(self, data, checkin_time, sch_checkin_time):
|
||
"""
|
||
保存相关的企业微信的原始打卡信息
|
||
:param data: 请求返回后的处理信息
|
||
:param checkin_time: 打卡时间
|
||
:param sch_checkin_time: 应打卡时间
|
||
:param media_list: 图片列表
|
||
:return: 保存结果
|
||
"""
|
||
userid = data.get("userid", "")
|
||
# 查询是否存在数据
|
||
if userid and checkin_time and self.env['hr.attendance.detail'].sudo().search_count(
|
||
[("userid", "=", userid), ("checkin_time", "=", checkin_time)]) <= 0:
|
||
res_user_obj = self.env['res.users'].sudo().search([("oauth_uid", "=", userid)], limit=1)
|
||
hr_employee_obj = self.env['hr.employee'].sudo().search([("user_id", "=", res_user_obj.id)], limit=1)
|
||
if not res_user_obj or not hr_employee_obj:
|
||
_logger.error('检查保存之前的数据异常,异常:无关联的员工数据,数据信息{0}'.format(str(data)))
|
||
return False
|
||
attendance_detail_obj = self.env['hr.attendance.detail'].sudo().create({
|
||
'employee_id': hr_employee_obj.id,
|
||
"userid": userid,
|
||
"groupname": data.get("groupname", ""),
|
||
"checkin_type": data.get("checkin_type", ""),
|
||
"exception_type": data.get("exception_type", ""),
|
||
"checkin_time": checkin_time,
|
||
"location_title": data.get("location_title", ""),
|
||
"location_detail": data.get("location_detail", ""),
|
||
"wifiname": data.get("wifiname", ""),
|
||
"notes": data.get("notes", ""),
|
||
"wifimac": data.get("wifimac", ""),
|
||
"mediaids": str(data.get("mediaids", "")),
|
||
"lat": data.get("lat", 0),
|
||
"lng": data.get("lng", 0),
|
||
"deviceid": data.get("deviceid", ""),
|
||
"sch_checkin_time": sch_checkin_time,
|
||
"groupid": data.get("groupid", 0),
|
||
"schedule_id": data.get("schedule_id", ""),
|
||
"timeline_id": data.get("timeline_id", 0),
|
||
# "attendance_detail_image_ids": media_list
|
||
})
|
||
_logger.info(f"{hr_employee_obj.name}----创建一条数据")
|
||
|
||
# 分析返回来的数据获取图片原图
|
||
try:
|
||
media_list = self._check_wx_attendance_media(data)
|
||
attendance_detail_obj.attendance_detail_image_ids = media_list
|
||
except Exception as e:
|
||
_logger.error('检查保存之前的数据分析出想要的数据失败,异常:{0},数据信息{1},异常详情:{2}'.format(str(e), str(data),
|
||
str(traceback.format_exc())))
|
||
|
||
def get_hr_attendance_time_wechat(self):
|
||
"""
|
||
获取企业微信打卡数据的核心逻辑
|
||
:return: 处理结果
|
||
"""
|
||
# 从用户列表里获取企业微信的用户
|
||
user_list = self._get_wx_attendance_user_list()
|
||
# now_time = datetime(2025, 10, 31) # 替换为你需要的年份
|
||
# start_time = datetime(2025, 10, 1) # 替换为你需要的年份
|
||
now_time = datetime.now() + timedelta(hours=8)
|
||
start_time = now_time - timedelta(days=25)
|
||
|
||
# 企业微信获取打卡数据,要求用户分组100,分批获取打卡记录
|
||
# for userid_limit in self.utils_list_page(user_list, 1):
|
||
|
||
for userid_limit in user_list:
|
||
# 获取企业微信的打卡信息
|
||
try:
|
||
attendance_data = self._get_wx_attendance_data(userid_limit, now_time, start_time)
|
||
attendance_result, response = self.sudo().env["wecom.apps"].sync_checkin_data(attendance_data)
|
||
except Exception as e:
|
||
_logger.error('获取企业微信的打卡信息失败,异常:{0},用户列表{1},异常详情:{2}'.format(str(e), str(userid_limit), str(traceback.format_exc())))
|
||
continue
|
||
# 检查请求返回的信息是否正确
|
||
try:
|
||
checkin_data = self._check_wx_attendance_data(response, userid_limit)
|
||
except Exception as e:
|
||
_logger.error('检查请求返回的信息失败,异常:{0},用户列表{1},异常详情:{2}'.format(str(e), str(userid_limit), str(traceback.format_exc())))
|
||
continue
|
||
# 将原始数据存储
|
||
for data in checkin_data:
|
||
# 检查保存之前的数据是否准确,并且分析出想要的打卡时间
|
||
try:
|
||
checkin_time, sch_checkin_time = self._check_wx_attendance_time(data, userid_limit)
|
||
except Exception as e:
|
||
_logger.error('检查保存之前的数据分析出想要的数据失败,异常:{0},数据信息{1},异常详情:{2}'.format(str(e), str(data), str(traceback.format_exc())))
|
||
continue
|
||
# 保存相关的企业微信的原始打卡信息
|
||
try:
|
||
self._save_wx_attendance_detail_data(data, checkin_time, sch_checkin_time)
|
||
except Exception as e:
|
||
_logger.error(
|
||
'保存相关的企业微信的原始打卡信息失败,异常:{0},数据信息{1},异常详情:{2}'.format(str(e),
|
||
str(data),
|
||
str(traceback.format_exc())))
|
||
|
||
for user in user_list:
|
||
for current_date in self.sudo().divide_time_range_fixed(start_time, now_time):
|
||
self.sudo()._do_save_wx_attendance_data(user, current_date)
|
||
self.sudo()._do_plan_save_wx_attendance_data(user, current_date)
|
||
|
||
def _do_save_wx_attendance_data(self, user, current_date):
|
||
"""
|
||
开始 整理考勤数据
|
||
:param user:
|
||
:param current_date:
|
||
:return:
|
||
"""
|
||
# 同步整理当天出勤数据
|
||
attendance_obj = self.env['hr.attendance'].search([("userid", "=", user), ("current_data", "=", current_date)], limit=1)
|
||
hr_employee_obj = self.env['hr.employee'].sudo().search([("user_id.oauth_uid", "=", user)], limit=1)
|
||
# if not attendance_obj:
|
||
# 判断是否是 普通打卡
|
||
# if not hr_employee_obj.default_planning_role_id:
|
||
# attendance_obj = self.env['hr.attendance'].sudo().create({
|
||
# 'employee_id': hr_employee_obj.id,
|
||
# "userid": user.oauth_uid,
|
||
# "current_data": current_date,
|
||
# "attendance_type": "wx_work"
|
||
# })
|
||
# _logger.info(f'创建一条考勤记录,员工:{hr_employee_obj.name}-日期{current_date}')
|
||
|
||
if not hr_employee_obj.default_planning_role_id:
|
||
if attendance_obj.is_approval:
|
||
# 已经审批修复无需再次进行更新数据
|
||
return True
|
||
self._do_not_planning_role(user, current_date, attendance_obj)
|
||
return True
|
||
|
||
def _do_plan_save_wx_attendance_data(self, user, current_date):
|
||
"""
|
||
开始 整理 计划排版的 考勤数据
|
||
:param user:
|
||
:param current_date:
|
||
:return:
|
||
"""
|
||
# 同步整理当天出勤数据
|
||
hr_employee_obj = self.env['hr.employee'].sudo().search([("user_id", "=", user)], limit=1)
|
||
current_datetime_start = datetime.combine(datetime.strptime(current_date, '%Y-%m-%d'), datetime.min.time())
|
||
current_datetime_end = datetime.combine(datetime.strptime(current_date, '%Y-%m-%d'), datetime.max.time())
|
||
# 判断是否是 排版打卡
|
||
if hr_employee_obj.default_planning_role_id:
|
||
planning_slot_ids = self.env['planning.slot'].search(
|
||
[('resource_id', '=', hr_employee_obj.resource_id.id),
|
||
('start_datetime', '>=', current_datetime_start - timedelta(hours=8)),
|
||
('start_datetime', '<=', current_datetime_end - timedelta(hours=8))])
|
||
for planning_slot_id in planning_slot_ids:
|
||
# 查询其中的一个排版信息是否生成了打卡记录
|
||
attendance_obj = self.env['hr.attendance'].search(
|
||
[("userid", "=", user), ("current_data", "=", current_date), ("shift_id", "=", planning_slot_id.id)
|
||
], limit=1)
|
||
|
||
# if not attendance_obj:
|
||
# self.env['hr.attendance'].sudo().create({
|
||
# 'employee_id': hr_employee_obj.id,
|
||
# "userid": user,
|
||
# "current_data": current_date,
|
||
# "shift_id": planning_slot_id.id,
|
||
# "attendance_type": "wx_work"
|
||
# })
|
||
# _logger.info(f'创建一条考勤记录,员工:{hr_employee_obj.name}-日期{current_date}')
|
||
if attendance_obj.is_approval:
|
||
# 已经审批修复无需再次进行更新数据
|
||
return True
|
||
|
||
self._do_planning_role(user, current_date, attendance_obj, planning_slot_id)
|
||
|
||
return True
|
||
|
||
def _do_not_planning_role(self, user, current_date, attendance_obj):
|
||
"""
|
||
正常班次的 上班下班判断
|
||
:param user: 用户集合
|
||
:param current_date: 循环判断的时间
|
||
:param attendance_obj: 判断的时刻的考勤集合
|
||
:return:
|
||
"""
|
||
current_datetime_start = datetime.combine(datetime.strptime(current_date, '%Y-%m-%d'), datetime.min.time())
|
||
current_datetime_end = datetime.combine(datetime.strptime(current_date, '%Y-%m-%d'), datetime.max.time())
|
||
hr_employee_obj = self.env['hr.employee'].sudo().search([("user_id.oauth_uid", "=", user)], limit=1)
|
||
# 白名单
|
||
resource_user_ids = hr_employee_obj.resource_calendar_id.resource_user_ids.ids
|
||
is_resource_user = True if hr_employee_obj.id in resource_user_ids else False
|
||
# 判断是否是星期
|
||
tz = hr_employee_obj.resource_calendar_id.tz
|
||
current_year = current_date.split('-')[0]
|
||
current_month = current_date.split('-')[1]
|
||
current_day = current_date.split('-')[2]
|
||
current_week = calendar.weekday(int(current_year), int(current_month), int(current_day))
|
||
work_time_result = self.get_employee_work_time(hr_employee_obj, current_week, current_date, tz)
|
||
# # 说明今天休息
|
||
# if not work_time_result:
|
||
# # 如果有人打开那么进行记录
|
||
#
|
||
# attendance_obj.sudo().write({
|
||
# "is_late_card": False,
|
||
# "is_early_card": False,
|
||
# "is_no_card": False,
|
||
# "is_abnormal": False,
|
||
# })
|
||
# return True
|
||
# 今天正常上班
|
||
# go_from, go_to, off_from, off_to, current_date = work_time_result
|
||
# 获取考勤日历的上班下班时间
|
||
min_hour_from = self.env['resource.calendar'].search([])[0].attendance_ids[0].hour_from
|
||
min_hour = int(min_hour_from)
|
||
min_minute = int(min_hour_from * 10 % min_hour_from)
|
||
max_hour_to = self.env['resource.calendar'].search([])[0].attendance_ids[-1].hour_to
|
||
max_hour = int(max_hour_to)
|
||
max_minute = int(max_hour_to * 10 % max_hour_to)
|
||
will_datetime_start = current_datetime_start.replace(hour=min_hour, minute=min_minute) - timedelta(hours=8)
|
||
will_datetime_end = current_datetime_end.replace(hour=max_hour, minute=max_minute) - timedelta(hours=8)
|
||
# 判断打卡情况排除未打卡情况
|
||
# 6:00 - 17:00 // 10.00 - // 5.00。
|
||
# 改为区间打卡
|
||
check_in_start = datetime.strptime(str(current_date) + " " + "06:00", "%Y-%m-%d %H:%M") - timedelta(hours=8)
|
||
check_in_end = datetime.strptime(str(current_date) + " " + "17:00", "%Y-%m-%d %H:%M") - timedelta(hours=8)
|
||
check_out_start = datetime.strptime(str(current_date) + " " + "10:00", "%Y-%m-%d %H:%M") - timedelta(hours=8)
|
||
check_out_end = datetime.strptime(str(current_date) + " " + "05:00", "%Y-%m-%d %H:%M") + relativedelta(days=1) - timedelta(hours=8)
|
||
|
||
# 说明今天休息
|
||
if not work_time_result:
|
||
# 如果有人打卡那么进行记录 相当于白名单
|
||
# 上班
|
||
attendance_detail_obj_in = self.env['hr.attendance.detail'].sudo().search(
|
||
[("exception_type", "!=", "未打卡"),
|
||
("userid", "=", user),
|
||
("checkin_time", ">=", check_in_start),
|
||
("checkin_time", "<=", check_in_end)])
|
||
if len(attendance_detail_obj_in) > 0:
|
||
# 关联 打卡详情
|
||
attendance_detail_obj_in.hr_attendance_id = attendance_obj.id
|
||
# 多次打卡时间的集合
|
||
checkin_time_list = [obj.checkin_time for obj in attendance_detail_obj_in]
|
||
if checkin_time_list:
|
||
# 最早 时间
|
||
min_time = max(checkin_time_list)
|
||
attendance_obj.sudo().write({
|
||
"sch_check_in_time": will_datetime_start,
|
||
"check_in": min_time,
|
||
"is_late_card": False,
|
||
"is_early_card": False,
|
||
"is_no_card": False,
|
||
"is_abnormal": False,
|
||
})
|
||
# _logger.info(f'更新一条考勤记录,日期-{attendance_obj.current_date}-最小时间{min_time}')
|
||
# 下班
|
||
attendance_detail_obj_out = self.env['hr.attendance.detail'].sudo().search(
|
||
[("exception_type", "!=", "未打卡"),
|
||
("userid", "=", user),
|
||
("checkin_time", ">=", check_out_start),
|
||
("checkin_time", "<=", check_out_end),
|
||
])
|
||
if len(attendance_detail_obj_out) > 0:
|
||
attendance_detail_obj_out.hr_attendance_id = attendance_obj.id
|
||
checkout_time_list = [obj.checkin_time for obj in attendance_detail_obj_out]
|
||
if checkout_time_list:
|
||
# 最早 时间
|
||
max_time = max(checkout_time_list)
|
||
# # 没有上班卡
|
||
# if not attendance_obj.check_in:
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_in_time": will_datetime_start,
|
||
# "check_in": max_time,
|
||
# "is_late_card": False,
|
||
# "is_no_card": False,
|
||
# "is_abnormal": False,
|
||
# "absenteeism_card": False,
|
||
# })
|
||
# elif attendance_obj.check_in and attendance_obj.check_in < max_time:
|
||
attendance_obj.sudo().write({
|
||
"sch_check_out_time": will_datetime_end,
|
||
"check_out": max_time,
|
||
"is_late_card": False,
|
||
"is_early_card": False,
|
||
"is_no_card": False,
|
||
"is_abnormal": False,
|
||
})
|
||
# _logger.info(f'更新一条考勤记录,日期-{attendance_obj.current_date}-最大时间{max_time}')
|
||
attendance_obj.sudo().write({
|
||
"is_late_card": False,
|
||
"is_early_card": False,
|
||
"is_no_card": False,
|
||
"is_abnormal": False,
|
||
})
|
||
|
||
return True
|
||
|
||
# 上班
|
||
attendance_detail_obj_in = self.env['hr.attendance.detail'].sudo().search(
|
||
[("exception_type", "!=", "未打卡"),
|
||
("userid", "=", user),
|
||
("checkin_time", ">=", check_in_start),
|
||
("checkin_time", "<=", check_in_end)])
|
||
# 关联 打卡详情
|
||
attendance_detail_obj_in.hr_attendance_id = attendance_obj.id
|
||
# 白名单用户直接正常
|
||
if is_resource_user:
|
||
if len(attendance_detail_obj_in) > 0:
|
||
# 多次打卡时间的集合
|
||
checkin_time_list = [obj.checkin_time for obj in attendance_detail_obj_in]
|
||
# 最早 时间
|
||
min_time = min(checkin_time_list)
|
||
attendance_obj.sudo().write({
|
||
"sch_check_in_time": will_datetime_start,
|
||
"check_in": min_time,
|
||
"is_late_card": False,
|
||
"is_no_card": False,
|
||
"is_abnormal": False,
|
||
"absenteeism_card": False,
|
||
})
|
||
# _logger.info(f'更新一条考勤记录,日期-{attendance_obj.current_date}-最小时间{min_time}')
|
||
else:
|
||
# 没有打卡记录说明缺卡
|
||
if len(attendance_detail_obj_in) <= 0:
|
||
attendance_obj.sudo().write({"is_no_card": True, "is_abnormal": False, "absenteeism_card": False})
|
||
else:
|
||
# 多次打卡时间的集合
|
||
checkin_time_list = [obj.checkin_time for obj in attendance_detail_obj_in]
|
||
if not checkin_time_list:
|
||
attendance_obj.sudo().write({"is_no_card": True, "is_abnormal": False, "absenteeism_card": False})
|
||
else:
|
||
# 最早 时间
|
||
min_time = min(checkin_time_list)
|
||
# 查询最早时间上班时间 查询最晚下班时间
|
||
# 判断时间 在10分钟内说明是 异常
|
||
if will_datetime_start + timedelta(minutes=10) > min_time > will_datetime_start + timedelta(minutes=1):
|
||
attendance_obj.sudo().write({
|
||
"sch_check_in_time": will_datetime_start,
|
||
"check_in": min_time,
|
||
"is_late_card": False,
|
||
"is_no_card": False,
|
||
"is_abnormal": True,
|
||
"absenteeism_card": False,
|
||
})
|
||
elif will_datetime_start + timedelta(hours=1) > min_time > will_datetime_start + timedelta(minutes=1):
|
||
attendance_obj.sudo().write({
|
||
"sch_check_in_time": will_datetime_start,
|
||
"check_in": min_time,
|
||
"is_late_card": True,
|
||
"is_no_card": False,
|
||
"is_abnormal": False,
|
||
"absenteeism_card": False,
|
||
})
|
||
elif min_time > will_datetime_start + timedelta(hours=1) and min_time > will_datetime_start + timedelta(minutes=1):
|
||
attendance_obj.sudo().write({
|
||
"sch_check_in_time": will_datetime_start,
|
||
"check_in": min_time,
|
||
"is_late_card": False,
|
||
"is_no_card": False,
|
||
"is_abnormal": False,
|
||
"absenteeism_card": True,
|
||
})
|
||
else:
|
||
attendance_obj.sudo().write({
|
||
"sch_check_in_time": will_datetime_start,
|
||
"check_in": min_time,
|
||
"is_late_card": False,
|
||
"is_no_card": False,
|
||
"is_abnormal": False,
|
||
"absenteeism_card": False,
|
||
})
|
||
# 下班
|
||
attendance_detail_obj_out = self.env['hr.attendance.detail'].sudo().search(
|
||
[("exception_type", "!=", "未打卡"),
|
||
("userid", "=", user),
|
||
("checkin_time", ">=", check_out_start),
|
||
("checkin_time", "<=", check_out_end),
|
||
])
|
||
# 关联 打卡详情
|
||
attendance_detail_obj_out.hr_attendance_id = attendance_obj.id
|
||
# 白名单用户直接正常
|
||
if is_resource_user:
|
||
# 多次打卡时间的集合
|
||
checkin_time_list = [obj.checkin_time for obj in attendance_detail_obj_out]
|
||
if checkin_time_list:
|
||
# 最早 时间
|
||
max_time = max(checkin_time_list)
|
||
# # 如果上班没有打卡
|
||
# if not attendance_obj.check_in:
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_in_time": will_datetime_start,
|
||
# "check_in": max_time,
|
||
# "is_late_card": False,
|
||
# "is_no_card": False,
|
||
# "is_abnormal": False,
|
||
# "absenteeism_card": False,
|
||
# })
|
||
# # 如果上班时间早于下班时间说明
|
||
# elif attendance_obj.check_in and attendance_obj.check_in < max_time:
|
||
attendance_obj.sudo().write({
|
||
"sch_check_out_time": will_datetime_end,
|
||
"check_out": max_time,
|
||
"is_early_card": False,
|
||
"is_no_card": False,
|
||
"is_abnormal": False,
|
||
"absenteeism_card": False,
|
||
})
|
||
else:
|
||
if len(attendance_detail_obj_out) <= 0:
|
||
attendance_obj.sudo().write({
|
||
"is_no_card": True,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
# 打了一次卡记录说明缺卡
|
||
else:
|
||
# 多次打卡时间的集合
|
||
checkin_time_list = [obj.checkin_time for obj in attendance_detail_obj_out]
|
||
if not checkin_time_list:
|
||
attendance_obj.sudo().write({
|
||
"is_no_card": True,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.is_abnormal else False,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
else:
|
||
# 最早 时间
|
||
max_time = max(checkin_time_list)
|
||
# # 如果上班没有打卡
|
||
# if not attendance_obj.check_in:
|
||
# if will_datetime_start + timedelta(minutes=10) > max_time > will_datetime_start + timedelta(minutes=1):
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_in_time": will_datetime_start,
|
||
# "check_in": max_time,
|
||
# "is_late_card": False,
|
||
# "is_no_card": True,
|
||
# "is_abnormal": True,
|
||
# "absenteeism_card": False,
|
||
# })
|
||
# elif will_datetime_start + timedelta(hours=1) > max_time > will_datetime_start + timedelta(minutes=1):
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_in_time": will_datetime_start,
|
||
# "check_in": max_time,
|
||
# "is_late_card": True,
|
||
# "is_no_card": True,
|
||
# "is_abnormal": False,
|
||
# "absenteeism_card": False,
|
||
# })
|
||
# elif max_time > will_datetime_start + timedelta(hours=1) and max_time > will_datetime_start + timedelta(minutes=1):
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_in_time": will_datetime_start,
|
||
# "check_in": max_time,
|
||
# "is_late_card": False,
|
||
# "is_no_card": True,
|
||
# "is_abnormal": False,
|
||
# "absenteeism_card": True,
|
||
# })
|
||
# else:
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_in_time": will_datetime_start,
|
||
# "check_in": max_time,
|
||
# "is_late_card": False,
|
||
# "is_no_card": True,
|
||
# "is_abnormal": False,
|
||
# "absenteeism_card": False,
|
||
# })
|
||
|
||
# 如果上班时间早于下班时间说明下班的卡没打
|
||
if attendance_obj.check_in:
|
||
if attendance_obj.check_in >= max_time:
|
||
attendance_obj.sudo().write({
|
||
"is_no_card": True,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.is_abnormal else False,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
else:
|
||
if will_datetime_end - timedelta(minutes=10) < max_time < will_datetime_end - timedelta(minutes=1):
|
||
attendance_obj.sudo().write({
|
||
"sch_check_out_time": will_datetime_end,
|
||
"check_out": max_time,
|
||
"is_early_card": False,
|
||
"is_no_card": True if attendance_obj.check_in and attendance_obj.is_no_card else False,
|
||
"is_abnormal": True,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
elif will_datetime_end - timedelta(hours=1) < max_time < will_datetime_end - timedelta(minutes=1):
|
||
attendance_obj.sudo().write({
|
||
"sch_check_out_time": will_datetime_end,
|
||
"check_out": max_time,
|
||
"is_early_card": True,
|
||
"is_no_card": True if attendance_obj.check_in and attendance_obj.is_no_card else False,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.is_abnormal else False,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
elif max_time < will_datetime_end - timedelta(hours=1):
|
||
attendance_obj.sudo().write({
|
||
"sch_check_out_time": will_datetime_end,
|
||
"check_out": max_time,
|
||
"is_early_card": False,
|
||
"is_no_card": True if attendance_obj.check_in and attendance_obj.is_no_card else False,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.is_abnormal else False,
|
||
"absenteeism_card": True,
|
||
})
|
||
else:
|
||
attendance_obj.sudo().write({
|
||
"sch_check_out_time": will_datetime_end,
|
||
"check_out": max_time,
|
||
"is_early_card": False,
|
||
"is_no_card": True if attendance_obj.check_in and attendance_obj.is_no_card else False,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.is_abnormal else False,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
return True
|
||
|
||
def _do_planning_role(self, user, current_date, attendance_obj, planning_id):
|
||
"""
|
||
带有计划的班次判断
|
||
:param user: 用户集合
|
||
:param current_date: 循环判断的时间
|
||
:param attendance_obj: 判断的时刻的考勤集合
|
||
:param planning_id:
|
||
:return:
|
||
"""
|
||
# 获取模板信息
|
||
template_id = planning_id.template_id
|
||
# check_in_start = fields.Datetime(string="上班开始")
|
||
check_in_start = template_id.check_in_start
|
||
# check_in_end = fields.Datetime(string="上班结束")
|
||
check_in_end = template_id.check_in_end
|
||
# check_out_start = fields.Datetime(string="下班开始")
|
||
check_out_start = template_id.check_out_start
|
||
# check_out_end = fields.Datetime(string="下班结束")
|
||
check_out_end = template_id.check_out_end
|
||
|
||
# 这里需要将 浮点型时间转为 时间型
|
||
if check_in_start:
|
||
check_in_start = self.str_to_time(current_date, check_in_start)
|
||
if check_in_end:
|
||
check_in_end = self.str_to_time(current_date, check_in_end)
|
||
if check_out_start:
|
||
check_out_start = self.str_to_time(current_date, check_out_start)
|
||
if check_out_end:
|
||
check_out_end = self.str_to_time(current_date, check_out_end)
|
||
|
||
current_datetime_start = datetime.combine(datetime.strptime(current_date, '%Y-%m-%d'), datetime.min.time())
|
||
current_datetime_end = datetime.combine(datetime.strptime(current_date, '%Y-%m-%d'), datetime.max.time())
|
||
# 如果没有设定区间那么默认当天最早时间和最晚时间 到 应该上下班时间
|
||
if not check_in_start:
|
||
check_in_start = current_datetime_start
|
||
if not check_in_end:
|
||
check_in_end = planning_id.start_datetime + timedelta(hours=8)
|
||
if not check_out_start:
|
||
check_out_start = planning_id.end_datetime + timedelta(hours=8)
|
||
if not check_out_end:
|
||
check_out_end = current_datetime_end
|
||
|
||
# 如果结束时间小于开始时间说明 是第二天的时间
|
||
if check_in_end < check_in_start:
|
||
check_in_end = check_in_end + relativedelta(days=1)
|
||
if check_out_end < check_out_start:
|
||
check_out_end = check_out_end + relativedelta(days=1)
|
||
|
||
# 转换时区
|
||
check_in_start = check_in_start - timedelta(hours=8)
|
||
check_in_end = check_in_end - timedelta(hours=8)
|
||
check_out_start = check_out_start - timedelta(hours=8)
|
||
check_out_end = check_out_end - timedelta(hours=8)
|
||
|
||
# 判断打卡情况排除未打卡情况
|
||
# 上班
|
||
attendance_detail_obj_in = self.env['hr.attendance.detail'].sudo().search(
|
||
[("exception_type", "!=", "未打卡"),
|
||
("userid", "=", user),
|
||
("checkin_time", ">=", check_in_start),
|
||
("checkin_time", "<=", check_in_end),
|
||
])
|
||
# 关联 打卡详情
|
||
attendance_detail_obj_in.hr_attendance_id = attendance_obj.id
|
||
|
||
will_datetime_start = planning_id.start_datetime
|
||
will_datetime_end = planning_id.end_datetime
|
||
|
||
if len(attendance_detail_obj_in) <= 0:
|
||
attendance_obj.sudo().write({"is_no_card": True, "is_abnormal": False, "absenteeism_card": False})
|
||
# 打了一次卡记录说明缺卡
|
||
else:
|
||
# 多次打卡时间的集合
|
||
checkin_time_list = [obj.checkin_time for obj in attendance_detail_obj_in]
|
||
if not checkin_time_list:
|
||
attendance_obj.sudo().write({"is_no_card": True, "is_abnormal": False, "absenteeism_card": False})
|
||
else:
|
||
# 最早 时间
|
||
min_time = min(checkin_time_list)
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_in_time": planning_id.start_datetime,
|
||
# "check_in": min_time,
|
||
# "is_late_card": min_time > planning_id.start_datetime,
|
||
# "is_no_card": False,
|
||
# "is_abnormal": False,
|
||
# })
|
||
if will_datetime_start + timedelta(minutes=10) > min_time > will_datetime_start:
|
||
attendance_obj.sudo().write({
|
||
"sch_check_in_time": will_datetime_start,
|
||
"check_in": min_time,
|
||
"is_late_card": False,
|
||
"is_no_card": False,
|
||
"is_abnormal": True,
|
||
"absenteeism_card": False,
|
||
})
|
||
elif will_datetime_start + timedelta(hours=1) > min_time > will_datetime_start:
|
||
attendance_obj.sudo().write({
|
||
"sch_check_in_time": will_datetime_start,
|
||
"check_in": min_time,
|
||
"is_late_card": True,
|
||
"is_no_card": False,
|
||
"is_abnormal": False,
|
||
"absenteeism_card": False,
|
||
})
|
||
elif min_time > will_datetime_start + timedelta(hours=1) and min_time > will_datetime_start:
|
||
attendance_obj.sudo().write({
|
||
"sch_check_in_time": will_datetime_start,
|
||
"check_in": min_time,
|
||
"is_late_card": False,
|
||
"is_no_card": False,
|
||
"is_abnormal": False,
|
||
"absenteeism_card": True,
|
||
})
|
||
else:
|
||
attendance_obj.sudo().write({
|
||
"sch_check_in_time": will_datetime_start,
|
||
"check_in": min_time,
|
||
"is_late_card": False,
|
||
"is_no_card": False,
|
||
"is_abnormal": False,
|
||
"absenteeism_card": False,
|
||
})
|
||
|
||
# 下班
|
||
attendance_detail_obj_out = self.env['hr.attendance.detail'].sudo().search(
|
||
[("exception_type", "!=", "未打卡"),
|
||
("userid", "=", user),
|
||
("checkin_time", ">=", check_out_start),
|
||
("checkin_time", "<=", check_out_end),
|
||
])
|
||
# 关联 打卡详情
|
||
attendance_detail_obj_out.hr_attendance_id = attendance_obj.id
|
||
|
||
if len(attendance_detail_obj_out) <= 0:
|
||
attendance_obj.sudo().write({
|
||
"is_no_card": True,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
# 打了一次卡记录说明缺卡
|
||
else:
|
||
# 多次打卡时间的集合
|
||
checkin_time_list = [obj.checkin_time for obj in attendance_detail_obj_out]
|
||
if not checkin_time_list:
|
||
attendance_obj.sudo().write({
|
||
"is_no_card": True,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
else:
|
||
# 最早 时间
|
||
max_time = max(checkin_time_list)
|
||
# 如果上班没有打卡
|
||
# if not attendance_obj.check_in:
|
||
# # attendance_obj.sudo().write({
|
||
# # "sch_check_in_time": planning_id.start_datetime,
|
||
# # "check_in": max_time,
|
||
# # "is_late_card": max_time > planning_id.start_datetime,
|
||
# # "is_no_card": True,
|
||
# # "is_abnormal": True,
|
||
# # })
|
||
#
|
||
# if will_datetime_start + timedelta(minutes=10) > max_time > will_datetime_start:
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_in_time": will_datetime_start,
|
||
# "check_in": max_time,
|
||
# "is_late_card": False,
|
||
# "is_no_card": True,
|
||
# "is_abnormal": True,
|
||
# "absenteeism_card": False,
|
||
# })
|
||
# elif will_datetime_start + timedelta(hours=1) > max_time > will_datetime_start:
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_in_time": will_datetime_start,
|
||
# "check_in": max_time,
|
||
# "is_late_card": True,
|
||
# "is_no_card": True,
|
||
# "is_abnormal": False,
|
||
# "absenteeism_card": False,
|
||
# })
|
||
# elif max_time > will_datetime_start + timedelta(hours=1) and max_time > will_datetime_start:
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_in_time": will_datetime_start,
|
||
# "check_in": max_time,
|
||
# "is_late_card": False,
|
||
# "is_no_card": True,
|
||
# "is_abnormal": False,
|
||
# "absenteeism_card": True,
|
||
# })
|
||
# else:
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_in_time": will_datetime_start,
|
||
# "check_in": max_time,
|
||
# "is_late_card": False,
|
||
# "is_no_card": True,
|
||
# "is_abnormal": False,
|
||
# "absenteeism_card": False,
|
||
# })
|
||
# 如果上班时间早于下班时间说明下班的卡没打
|
||
if attendance_obj.check_in:
|
||
if attendance_obj.check_in >= max_time:
|
||
attendance_obj.sudo().write({
|
||
"is_no_card": True,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
else:
|
||
# attendance_obj.sudo().write({
|
||
# "sch_check_out_time": planning_id.end_datetime,
|
||
# "check_out": max_time,
|
||
# "is_early_card": max_time < planning_id.end_datetime,
|
||
# "is_no_card": False,
|
||
# "is_abnormal": False,
|
||
# })
|
||
if will_datetime_end - timedelta(minutes=10) < max_time < will_datetime_end:
|
||
attendance_obj.sudo().write({
|
||
"sch_check_out_time": will_datetime_end,
|
||
"check_out": max_time,
|
||
"is_early_card": False,
|
||
"is_no_card": True if attendance_obj.check_in and attendance_obj.is_no_card else False,
|
||
"is_abnormal": True,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
elif will_datetime_end - timedelta(hours=1) < max_time < will_datetime_end:
|
||
attendance_obj.sudo().write({
|
||
"sch_check_out_time": will_datetime_end,
|
||
"check_out": max_time,
|
||
"is_early_card": True,
|
||
"is_no_card": True if attendance_obj.check_in and attendance_obj.is_no_card else False,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.is_abnormal else False,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
elif max_time < will_datetime_end - timedelta(hours=1):
|
||
attendance_obj.sudo().write({
|
||
"sch_check_out_time": will_datetime_end,
|
||
"check_out": max_time,
|
||
"is_early_card": False,
|
||
"is_no_card": True if attendance_obj.check_in and attendance_obj.is_no_card else False,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.is_abnormal else False,
|
||
"absenteeism_card": True,
|
||
})
|
||
else:
|
||
attendance_obj.sudo().write({
|
||
"sch_check_out_time": will_datetime_end,
|
||
"check_out": max_time,
|
||
"is_early_card": False,
|
||
"is_no_card": True if attendance_obj.check_in and attendance_obj.is_no_card else False,
|
||
"is_abnormal": True if attendance_obj.check_in and attendance_obj.is_abnormal else False,
|
||
"absenteeism_card": True if attendance_obj.check_in and attendance_obj.absenteeism_card else False,
|
||
})
|
||
return True
|
||
|
||
@staticmethod
|
||
def divide_time_range_fixed(start_time, end_time):
|
||
time_ranges = [start_time.strftime("%Y-%m-%d")]
|
||
while start_time + relativedelta(days=1) <= end_time:
|
||
current_date = start_time + relativedelta(days=1)
|
||
time_ranges.append(current_date.strftime("%Y-%m-%d"))
|
||
start_time = current_date
|
||
return time_ranges
|
||
|
||
@staticmethod
|
||
def str_to_time(current_data, data):
|
||
"""
|
||
%d:%d 的 str类型的时间转换成时间
|
||
:param current_data: 当前日期
|
||
:param data: str类型时间
|
||
:return: 转换后的时间
|
||
"""
|
||
if data >= 24:
|
||
data = 23.99
|
||
from_time = "%d:%d" % (int(data), round((data - int(data)) * 60))
|
||
from_times = str(current_data) + " " + from_time
|
||
from_ti = datetime.strptime(from_times, "%Y-%m-%d %H:%M")
|
||
return from_ti
|
||
|
||
|
||
@staticmethod
|
||
def utils_list_page(data, limit):
|
||
"""
|
||
列表平分
|
||
:param data: list
|
||
:param limit: 每组多少个
|
||
:return: 平分结果
|
||
"""
|
||
for start in range(0, len(data), limit):
|
||
if start + limit > len(data):
|
||
end = len(data)
|
||
else:
|
||
end = start + limit
|
||
yield data[start:end]
|
||
|
||
def get_employee_work_time(self, employee, current_week, current_date, tz):
|
||
"""
|
||
获取员工的当天是否应该出勤以及出勤时间
|
||
:param employee: 员工
|
||
:param current_week: 当天周
|
||
:param current_date: 当前天
|
||
:param tz: 时区
|
||
:return: 出勤时间
|
||
"""
|
||
# 获取员工的工作信息
|
||
resource_calendar = employee.resource_calendar_id
|
||
# 获取工作表
|
||
attendance_ids = resource_calendar.attendance_ids
|
||
# 获取公休息日表
|
||
global_leave = resource_calendar.global_leave_ids
|
||
# 有没有工作规则
|
||
if not attendance_ids:
|
||
return False
|
||
# 判断是否是调休,如果是直接返回当天上班
|
||
if self.get_leave_rest_day(global_leave, current_date):
|
||
return True
|
||
# 是不是工作时间
|
||
attendance_data = resource_calendar.attendance_ids.filtered(
|
||
lambda s: s.dayofweek == str(current_week) and s.day_period in ["morning", "afternoon"])
|
||
if not attendance_data:
|
||
return False
|
||
# 获取工作时间
|
||
work_time = self.get_work_time(attendance_data, current_date, tz)
|
||
if not work_time:
|
||
return False
|
||
go_from, go_to, off_from, off_to, current_date = work_time
|
||
# 早上和下午上班 判断当天 是不是公休日
|
||
if self.get_leave_day(global_leave, go_from, tz) and self.get_leave_day(global_leave, off_from, tz):
|
||
return False
|
||
return go_from, go_to, off_from, off_to, current_date
|
||
|
||
def get_work_time(self, attendance_data, current_date, tz):
|
||
"""
|
||
当前时间的工作时间段
|
||
:param attendance_data:
|
||
:param current_date:
|
||
:param tz:
|
||
:return:
|
||
"""
|
||
work_time_list = []
|
||
for work_time in attendance_data:
|
||
work_time_list.append(self.time_to_utc_time(self.str_to_time(current_date, work_time.hour_from), tz))
|
||
work_time_list.append(self.time_to_utc_time(self.str_to_time(current_date, work_time.hour_to), tz))
|
||
work_time_sorted = sorted(work_time_list)
|
||
go_from, go_to, off_from, off_to = work_time_sorted
|
||
return go_from, go_to, off_from, off_to, current_date
|
||
|
||
@staticmethod
|
||
def time_to_utc_time(data, tz):
|
||
"""
|
||
本地时间转换成tz时间
|
||
:param data: 时间
|
||
:param tz: 时区
|
||
:return: 转换时间
|
||
"""
|
||
time_timezone = pytz.timezone(tz).localize(data, is_dst=False)
|
||
time_to_utc = time_timezone.astimezone(pytz.timezone("utc"))
|
||
utc_time_str = time_to_utc.strftime("%Y-%m-%d %H:%M:%S")
|
||
utc_time = datetime.strptime(utc_time_str, "%Y-%m-%d %H:%M:%S")
|
||
return utc_time
|
||
|
||
def get_leave_rest_day(self, global_leave, current_time):
|
||
"""
|
||
判断是否是公休日的调休日
|
||
:param global_leave:
|
||
:param current_time:
|
||
:param tz:
|
||
:return:
|
||
"""
|
||
if not global_leave:
|
||
return False
|
||
for leave in global_leave:
|
||
if leave.shift_replacement_day:
|
||
for rest_day in str(leave.shift_replacement_day).split(","):
|
||
# rest_day_month = str(rest_day).split("月")[0]
|
||
rest_day_month = datetime.strptime(rest_day, "%m月%d日").strftime("%m")
|
||
# rest_day_day = str(str(rest_day).split("月")[1].replace("月", "")).split("日")[0]
|
||
rest_day_day = datetime.strptime(rest_day, "%m月%d日").strftime("%d")
|
||
current_time_month = current_time.split('-')[1]
|
||
current_time_day = current_time.split('-')[2]
|
||
if int(rest_day_month) == int(current_time_month) and int(rest_day_day) == int(current_time_day):
|
||
return True
|
||
return False
|
||
|
||
def get_leave_day(self, global_leave, current_time, tz):
|
||
"""
|
||
判断是否是公休日
|
||
:param global_leave:
|
||
:param current_time:
|
||
:param tz:
|
||
:return:
|
||
"""
|
||
if not global_leave:
|
||
return False
|
||
for leave in global_leave:
|
||
if self.utc_time_to_tz(tz, leave.date_from) <= current_time.strftime("%Y-%m-%d %H:%M:%S") <= self.utc_time_to_tz(tz, leave.date_to):
|
||
return True
|
||
return False
|
||
|
||
@staticmethod
|
||
def utc_time_to_tz(tz, chin):
|
||
"""
|
||
utc时间的时区转换tz时间
|
||
:param tz: 时区
|
||
:param chin: 时间
|
||
:return: 转换时间
|
||
"""
|
||
time_timezone = pytz.timezone('utc').localize(chin, is_dst=False)
|
||
time_to_utc = time_timezone.astimezone(pytz.timezone(tz))
|
||
utc_time_str = time_to_utc.strftime("%Y-%m-%d %H:%M:%S")
|
||
return utc_time_str |