862 lines
47 KiB
Python
862 lines
47 KiB
Python
# -*- coding: utf-8 -*-
|
|
import os
|
|
import base64
|
|
from datetime import datetime, timedelta, time
|
|
import logging
|
|
import traceback
|
|
from odoo import api, fields, models, tools, _
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
_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)
|
|
department_id = fields.Many2one('hr.department', string="部门", related="employee_id.department_id", readonly=True)
|
|
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')
|
|
|
|
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)
|
|
|
|
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}
|
|
}
|
|
|
|
def _get_wx_attendance_user_list(self):
|
|
"""
|
|
获取企业微信的关联用户
|
|
:return: 用户列表
|
|
"""
|
|
# userid 从用户列表里获取企业微信的用户
|
|
user_obj = self.env['res.users'].search([])
|
|
# 暂时先用for循环,如果后期加大频率,需改成并发
|
|
user_list = []
|
|
for user in user_obj:
|
|
if user.oauth_uid:
|
|
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:
|
|
"""
|
|
# 先按照天抓取,时间戳
|
|
# now_time = datetime.now() + timedelta(hours=8)
|
|
# start_time = now_time - timedelta(days=30)
|
|
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('抓取出勤数据请求失败,异常:{0},用户列表{1}'.format(str(response.json()), str(userid_limit)))
|
|
return []
|
|
result = response.json()
|
|
if result.get('errcode') != 0:
|
|
_logger.error(
|
|
'获取外出打卡数据失败,异常:{0},用户列表{1}'.format(str(result.get('errmsg')), str(userid_limit)))
|
|
return []
|
|
checkin_data = result.get('checkindata', [])
|
|
if not checkin_data:
|
|
_logger.error('没有找到外出打卡记录,异常:{0},用户列表{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
|
|
})
|
|
|
|
# 分析返回来的数据获取图片原图
|
|
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 _check_wx_attendance_data(self, data, checkin_time, sch_checkin_time):
|
|
# userid = data.get("userid", "")
|
|
# # 区分当天的上班,下班,中午打卡,合并一个
|
|
# checkin_time_now = checkin_time + timedelta(hours=8) if checkin_time else None
|
|
# sch_checkin_time_now = sch_checkin_time + timedelta(hours=8) if sch_checkin_time else None
|
|
# current_data = (sch_checkin_time + timedelta(hours=8)).strftime("%Y-%m-%d") if sch_checkin_time_now else checkin_time_now.strftime("%Y-%m-%d")
|
|
# # 同步整理当天出勤数据
|
|
# attendance_obj = self.env['hr.attendance'].search(
|
|
# [("userid", "=", userid), ("current_data", "=", current_data)], limit=1)
|
|
# attendance_obj.attendance_type = "wx_work"
|
|
# checkin_type = data.get("checkin_type", "")
|
|
# exception_type = data.get("exception_type", "")
|
|
# if not attendance_obj:
|
|
# res_user_obj = self.env['res.users'].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 hr_employee_obj.default_planning_role_id:
|
|
# attendance_obj = self.env['hr.attendance'].sudo().create({
|
|
# 'employee_id': hr_employee_obj.id,
|
|
# "userid": userid,
|
|
# "current_data": current_data,
|
|
# "attendance_type": "wx_work"
|
|
# })
|
|
# else:
|
|
# current_datetime_start = datetime.combine(datetime.strptime(current_data, '%Y-%m-%d'), datetime.min.time())
|
|
# current_datetime_end = datetime.combine(datetime.strptime(current_data, '%Y-%m-%d'), datetime.max.time())
|
|
# planning_slot_id = self.env['planning.slot'].search(
|
|
# [('resource_id', '=', hr_employee_obj.resource_id.id),
|
|
# ('start_datetime', '>=', current_datetime_start),
|
|
# ('start_datetime', '<=', current_datetime_end)])
|
|
# if planning_slot_id:
|
|
# attendance_obj = self.env['hr.attendance'].sudo().create({
|
|
# 'employee_id': hr_employee_obj.id,
|
|
# "userid": userid,
|
|
# "current_data": current_data,
|
|
# "shift_id": planning_slot_id.id,
|
|
# "attendance_type": "wx_work"
|
|
# })
|
|
# if not hr_employee_obj.default_planning_role_id:
|
|
# # 获取考勤日历的上班下班时间
|
|
# 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([])[-1].attendance_ids[0].hour_to
|
|
# max_hour = int(max_hour_to)
|
|
# max_minute = int(max_hour_to * 10 % max_hour_to)
|
|
# if attendance_obj.is_approval:
|
|
# # 已经审批修复无需再次进行更新数据
|
|
# return True
|
|
# # 赋值企业微信打卡数据
|
|
# will_datetime_start = checkin_time.replace(hour=min_hour, minute=min_minute)
|
|
# will_datetime_end = checkin_time.replace(hour=max_hour, minute=max_minute)
|
|
# if exception_type != "未打卡":
|
|
# if checkin_time and checkin_type == "上班打卡" and exception_type != "未打卡" and not attendance_obj.check_in:
|
|
# attendance_obj.sudo().write({
|
|
# "sch_check_in_time": sch_checkin_time,
|
|
# "check_in": checkin_time,
|
|
# "is_late_card": checkin_time > will_datetime_start,
|
|
# })
|
|
# elif checkin_time and checkin_type == "下班打卡" and exception_type != "未打卡" and not attendance_obj.check_out:
|
|
# attendance_obj.sudo().write({
|
|
# "sch_check_out_time": sch_checkin_time,
|
|
# "check_out": checkin_time,
|
|
# "is_early_card": will_datetime_end > checkin_time,
|
|
# })
|
|
# elif attendance_obj.check_in and checkin_time and checkin_type == "上班打卡" and exception_type != "未打卡" and checkin_time < attendance_obj.check_in:
|
|
# attendance_obj.sudo().write({
|
|
# "sch_check_in_time": sch_checkin_time,
|
|
# "check_in": checkin_time,
|
|
# "is_late_card": checkin_time > will_datetime_start,
|
|
# })
|
|
# elif attendance_obj.check_out and checkin_time and checkin_type == "下班打卡" and exception_type != "未打卡" and checkin_time > attendance_obj.check_out:
|
|
# attendance_obj.sudo().write({
|
|
# "sch_check_out_time": sch_checkin_time,
|
|
# "check_out": checkin_time,
|
|
# "is_early_card": will_datetime_end > checkin_time,
|
|
# })
|
|
# elif checkin_time and exception_type != "未打卡" and not attendance_obj.check_in and not attendance_obj.check_out:
|
|
# attendance_obj.sudo().write({
|
|
# "sch_check_in_time": sch_checkin_time,
|
|
# "check_in": checkin_time,
|
|
# "is_late_card": checkin_time > will_datetime_start,
|
|
# })
|
|
# elif checkin_time and exception_type != "未打卡" and attendance_obj.check_in and not attendance_obj.check_out:
|
|
# attendance_obj.sudo().write({
|
|
# "sch_check_out_time": sch_checkin_time,
|
|
# "check_out": checkin_time,
|
|
# "is_early_card": will_datetime_end > checkin_time,
|
|
# })
|
|
# elif attendance_obj.is_early_card or attendance_obj.is_late_card:
|
|
# attendance_obj.sudo().write({"is_abnormal": True})
|
|
# else:
|
|
# attendance_obj.sudo().write({"is_no_card": True, "is_abnormal": True})
|
|
# else:
|
|
# current_data = datetime.strptime(current_data, '%Y-%m-%d')
|
|
# # 获取当天的起始和结束时间
|
|
# current_datetime_start = datetime.combine(current_data, datetime.min.time())
|
|
# current_datetime_end = datetime.combine(current_data, datetime.max.time())
|
|
# # 获取本人当天的班次
|
|
# planning_id = self.env['planning.slot'].search([('resource_id', '=', hr_employee_obj.resource_id.id),
|
|
# ('start_datetime', '>=', current_datetime_start),
|
|
# ('start_datetime', '<=', current_datetime_end)])
|
|
# if planning_id:
|
|
# planning_start_time = planning_id.start_datetime + timedelta(hours=8)
|
|
# planning_end_time = planning_id.end_datetime + timedelta(hours=8)
|
|
# if planning_id.role_id.name == '保安部':
|
|
# # {'白班': [['8:30', '9.30'], ['8:30', '9.30']]}white
|
|
# # {'中班': [['8:30', '9.30'], ['8:30', '9.30']]}mid
|
|
# # {'夜班': [['8:30', '9.30'], ['8:30', '9.30']]}night
|
|
# # 白班上班区间
|
|
# start_time = datetime.combine(current_data, time(6, 0)) + timedelta(hours=8)
|
|
# end_time = datetime.combine(current_data, time(9, 0)) + timedelta(hours=8)
|
|
# # 白班下班区间
|
|
# start_time1 = datetime.combine(current_data, time(2, 0)) + timedelta(hours=8)
|
|
# end_time1 = datetime.combine(current_data, time(4, 0)) + timedelta(hours=8)
|
|
# # 中班上班区间
|
|
# start_time2 = datetime.combine(current_data, time(14, 0)) + timedelta(hours=8)
|
|
# end_time2 = datetime.combine(current_data, time(17, 0)) + timedelta(hours=8)
|
|
# # 中班下班区间
|
|
# start_time3 = datetime.combine(current_data, time(22, 0)) + timedelta(hours=8)
|
|
# end_time3 = datetime.combine(current_data, time(1, 0)) + timedelta(hours=8)
|
|
# # 夜班上班区间
|
|
# start_time4 = datetime.combine(current_data, time(22, 0)) + timedelta(hours=8)
|
|
# end_time4 = datetime.combine(current_data, time(1, 0)) + timedelta(hours=8)
|
|
# # 夜班下班区间
|
|
# start_time5 = datetime.combine(current_data, time(6, 0)) + timedelta(hours=8)
|
|
# end_time5 = datetime.combine(current_data, time(9, 0)) + timedelta(hours=8)
|
|
# if checkin_time:
|
|
# planning_name = planning_id.template_id.planning_name
|
|
# if start_time <= checkin_time <= end_time and planning_name == '白班':
|
|
# attendance_obj.sudo().write({
|
|
# "check_in": checkin_time,
|
|
# "is_late_card": checkin_time > planning_start_time
|
|
# })
|
|
# elif start_time1 <= checkin_time <= end_time1 and planning_name == '白班':
|
|
# attendance_obj.sudo().write({
|
|
# "check_out": checkin_time,
|
|
# "is_early_card": planning_end_time > checkin_time,
|
|
# })
|
|
# elif start_time2 <= checkin_time <= end_time2 and planning_name == '中班':
|
|
# attendance_obj.sudo().write({
|
|
# "check_in": checkin_time,
|
|
# "is_late_card": checkin_time > planning_start_time
|
|
# })
|
|
# elif start_time3 <= checkin_time <= end_time3 and planning_name == '中班':
|
|
# attendance_obj.sudo().write({
|
|
# "check_out": checkin_time,
|
|
# "is_early_card": planning_end_time > checkin_time,
|
|
# })
|
|
# elif start_time4 <= checkin_time <= end_time4 and planning_name == '夜班':
|
|
# attendance_obj.sudo().write({
|
|
# "check_in": checkin_time,
|
|
# "is_late_card": checkin_time > planning_start_time
|
|
# })
|
|
# elif start_time5 <= checkin_time <= end_time5 and planning_name == '夜班':
|
|
# attendance_obj.sudo().write({
|
|
# "check_out": checkin_time,
|
|
# "is_early_card": planning_end_time > checkin_time,
|
|
# })
|
|
# elif attendance_obj.is_early_card or attendance_obj.is_late_card:
|
|
# attendance_obj.sudo().write({"is_abnormal": True})
|
|
# else:
|
|
# attendance_obj.sudo().write({"is_no_card": True, "is_abnormal": True})
|
|
# # elif planning_id.role_id.name == '工程部':
|
|
# # # 白班和白值上班区间
|
|
# # start_time = datetime.combine(current_data, time(7, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # end_time = datetime.combine(current_data, time(10, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # # 白班和白值下班区间
|
|
# # start_time1 = datetime.combine(current_data,
|
|
# # time(16, 0)) + timedelta(hours=8)
|
|
# # end_time1 = datetime.combine(current_data, time(19, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # # 白值值班打卡区间
|
|
# # start_time2 = datetime.combine(current_data,
|
|
# # time(11, 30)) + timedelta(hours=8)
|
|
# # end_time2 = datetime.combine(current_data, time(13, 30)) + timedelta(
|
|
# # hours=8)
|
|
# # # 中夜上班区间 中夜下班区间跟白班上班区间一样
|
|
# # start_time3 = datetime.combine(current_data,
|
|
# # time(13, 0)) + timedelta(hours=8)
|
|
# # end_time3 = datetime.combine(current_data, time(16, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # # 中夜值班打卡区间
|
|
# # start_time4 = datetime.combine(current_data,
|
|
# # time(20, 0)) + timedelta(hours=8)
|
|
# # end_time4 = datetime.combine(current_data, time(23, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # if planning_id:
|
|
# # if checkin_time:
|
|
# # if start_time <= checkin_time <= end_time and not attendance_obj.check_in and (
|
|
# # planning_name in ['白班', '白值']):
|
|
# # attendance_obj.sudo().write({
|
|
# # "check_in": checkin_time,
|
|
# # "is_late_card": checkin_time > planning_start_time
|
|
# # })
|
|
# # elif end_time1 <= checkin_time <= start_time1 and not attendance_obj.check_out and (
|
|
# # planning_name in ['白班', '白值']):
|
|
# # attendance_obj.sudo().write({
|
|
# # "check_out": checkin_time,
|
|
# # "is_early_card": planning_end_time > checkin_time,
|
|
# # })
|
|
# # elif start_time3 <= checkin_time <= end_time3 and not attendance_obj.check_in and planning_name == '中夜':
|
|
# # attendance_obj.sudo().write({
|
|
# # "check_in": checkin_time,
|
|
# # "is_late_card": checkin_time > planning_start_time
|
|
# # })
|
|
# # elif start_time <= checkin_time <= end_time and not attendance_obj.check_out and planning_name == '中夜':
|
|
# # attendance_obj.sudo().write({
|
|
# # "check_out": checkin_time,
|
|
# # "is_early_card": checkin_time > planning_start_time
|
|
# # })
|
|
# # elif planning_id.template_id.planning == '白值' and start_time2 <= checkin_time <= end_time2:
|
|
# # attendance_obj.sudo().write({
|
|
# # "sch_check_out_time": sch_checkin_time,
|
|
# # "noon_duty": checkin_time,
|
|
# # })
|
|
# # elif planning_id.template_id.planning == '中夜' and start_time4 <= checkin_time <= end_time4:
|
|
# # attendance_obj.sudo().write({
|
|
# # "sch_check_out_time": sch_checkin_time,
|
|
# # "night_duty": checkin_time,
|
|
# # })
|
|
# # # 异常情况处理,移到所有打卡逻辑之后
|
|
# # if (attendance_obj.is_early_card or attendance_obj.is_late_card or
|
|
# # (planning_id.template_id.planning == '白值' and not attendance_obj.noon_duty) or
|
|
# # (planning_id.template_id.planning == '中夜' and not attendance_obj.night_duty)):
|
|
# # attendance_obj.sudo().write({"is_abnormal": True})
|
|
# # else:
|
|
# # attendance_obj.sudo().write({"is_no_card": True, "is_abnormal": True})
|
|
# # elif planning_id.role_id.name == '联安粮储':
|
|
# # # 白班上班区间
|
|
# # start_time = datetime.combine(current_data, time(7, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # end_time = datetime.combine(current_data, time(10, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # # 白班下班区间
|
|
# # start_time1 = datetime.combine(current_data,
|
|
# # time(15, 0)) + timedelta(hours=8)
|
|
# # end_time1 = datetime.combine(current_data, time(18, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # # 白值上班
|
|
# # start_time2 = datetime.combine(current_data, time(8, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # end_time2 = datetime.combine(current_data, time(10, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # # 白值下班
|
|
# # start_time4 = datetime.combine(current_data, time(4, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # end_time4 = datetime.combine(current_data, time(7, 0)) + timedelta(
|
|
# # hours=8)
|
|
# # # 白值打卡区间、
|
|
# # start_time3 = datetime.combine(current_data,
|
|
# # time(11, 30)) + timedelta(hours=8)
|
|
# # end_time3 = datetime.combine(current_data, time(13, 30)) + timedelta(
|
|
# # hours=8)
|
|
# # if planning_id:
|
|
# # if start_time <= checkin_time <= end_time and not attendance_obj.check_in and planning_name == '白班':
|
|
# # attendance_obj.sudo().write({
|
|
# # "check_in": checkin_time,
|
|
# # "is_late_card": checkin_time > planning_start_time
|
|
# # })
|
|
# # elif start_time1 <= checkin_time <= end_time1 and not attendance_obj.check_out and planning_name == '白班':
|
|
# # attendance_obj.sudo().write({
|
|
# # "check_out": checkin_time,
|
|
# # "is_early_card": planning_end_time > checkin_time,
|
|
# # })
|
|
# # elif start_time2 <= checkin_time <= end_time2 and not attendance_obj.check_in and planning_name == '白值':
|
|
# # attendance_obj.sudo().write({
|
|
# # "check_in": checkin_time,
|
|
# # "is_late_card": checkin_time > planning_start_time,
|
|
# # })
|
|
# # elif start_time4 <= checkin_time <= end_time4 and not attendance_obj.check_out and planning_name == '白值':
|
|
# # attendance_obj.sudo().write({
|
|
# # "check_out": checkin_time,
|
|
# # "is_early_card": planning_end_time > checkin_time,
|
|
# # })
|
|
# # elif start_time3 <= checkin_time <= end_time3 and planning_name == '白值':
|
|
# # attendance_obj.sudo().write({
|
|
# # "noon_duty": checkin_time,
|
|
# # })
|
|
# # if (attendance_obj.is_early_card or attendance_obj.is_late_card or
|
|
# # (planning_id.template_id.planning == '白值班' and not attendance_obj.noon_duty)):
|
|
# # attendance_obj.sudo().write({"is_abnormal": True})
|
|
# # else:
|
|
# # attendance_obj.sudo().write({"is_no_card": True, "is_abnormal": True})
|
|
# # 进行关联
|
|
# attendance_detail_obj.hr_attendance_id = attendance_obj.id
|
|
# return True
|
|
|
|
def get_hr_attendance_time(self):
|
|
"""
|
|
获取企业微信打卡数据的核心逻辑
|
|
:return: 处理结果
|
|
"""
|
|
# 从用户列表里获取企业微信的用户
|
|
user_list = self._get_wx_attendance_user_list()
|
|
now_time = datetime.now() + timedelta(hours=8)
|
|
start_time = now_time - timedelta(days=30)
|
|
|
|
# 企业微信获取打卡数据,要求用户分组100,分批获取打卡记录
|
|
for userid_limit in self.utils_list_page(user_list, 100):
|
|
# 获取企业微信的打卡信息
|
|
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())))
|
|
|
|
# 将取回来的数据进行组织整理写入到考勤表中
|
|
user_obj = self.env['res.users'].search([])
|
|
# 暂时先用for循环,如果后期加大频率,需改成并发
|
|
for user in user_obj:
|
|
# 判断是否是微信用户
|
|
if user.oauth_uid:
|
|
# 查询当前用户区间内的所有打卡数据没有的创建
|
|
for current_date in self.divide_time_range_fixed(start_time, now_time):
|
|
# 普通用户写入打卡数据
|
|
self._do_save_wx_attendance_data(user, current_date)
|
|
# 计划用户的打开时间
|
|
# self._do_plan_save_wx_attendance_data(user, current_date)
|
|
return True
|
|
|
|
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.oauth_uid), ("current_data", "=", current_date)], limit=1)
|
|
hr_employee_obj = self.env['hr.employee'].sudo().search([("user_id", "=", user.id)], 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 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"
|
|
})
|
|
|
|
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)
|
|
|
|
# 查询出当前抓取微信的当前时间的所有数据
|
|
attendance_detail_obj = self.env['hr.attendance.detail'].sudo().search(
|
|
[("userid", "=", user.oauth_uid),
|
|
("checkin_time", ">=", current_datetime_start - timedelta(hours=8)),
|
|
("checkin_time", "<=", current_datetime_end - timedelta(hours=8))])
|
|
# 关联 打卡详情
|
|
attendance_detail_obj.hr_attendance_id = attendance_obj.id
|
|
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.id)], 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),
|
|
('start_datetime', '<=', current_datetime_end)])
|
|
for planning_slot_id in planning_slot_ids:
|
|
# 查询其中的一个排版信息是否生成了打卡记录
|
|
attendance_obj = self.env['hr.attendance'].search(
|
|
[("userid", "=", user.oauth_uid), ("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.oauth_uid,
|
|
"current_data": current_date,
|
|
"shift_id": planning_slot_id.id,
|
|
"attendance_type": "wx_work"
|
|
})
|
|
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())
|
|
# 获取考勤日历的上班下班时间
|
|
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)
|
|
# 判断打卡情况排除未打卡情况
|
|
attendance_detail_obj = self.env['hr.attendance.detail'].sudo().search(
|
|
[("exception_type", "!=", "未打卡"),
|
|
("userid", "=", user.oauth_uid),
|
|
("checkin_time", ">=", current_datetime_start - timedelta(hours=8)),
|
|
("checkin_time", "<=", current_datetime_end - timedelta(hours=8))])
|
|
# 没有打卡记录说明缺卡
|
|
if len(attendance_detail_obj) <= 0:
|
|
attendance_obj.sudo().write({"is_no_card": True, "is_abnormal": True})
|
|
return True
|
|
# 打了一次卡记录说明缺卡
|
|
elif len(attendance_detail_obj) == 1:
|
|
checkin_time = attendance_detail_obj.checkin_time
|
|
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)
|
|
# 既不是上班也不是下班,默认为上班打卡
|
|
attendance_obj.sudo().write({
|
|
"sch_check_in_time": will_datetime_start,
|
|
"check_in": checkin_time,
|
|
"is_late_card": checkin_time > will_datetime_start,
|
|
"is_no_card": True,
|
|
"is_abnormal": True,
|
|
})
|
|
return True
|
|
else:
|
|
# 多次打卡时间的集合
|
|
checkin_time_list = [obj.checkin_time for obj in attendance_detail_obj]
|
|
# 最早 和 最晚 时间
|
|
min_time = min(checkin_time_list)
|
|
max_time = max(checkin_time_list)
|
|
|
|
# 赋值企业微信打卡数据
|
|
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)
|
|
# 查询最早时间上班时间 查询最晚下班时间
|
|
attendance_obj.sudo().write({
|
|
"sch_check_in_time": will_datetime_start,
|
|
"check_in": min_time,
|
|
"is_late_card": min_time > will_datetime_start,
|
|
"sch_check_out_time": will_datetime_end,
|
|
"check_out": max_time,
|
|
"is_early_card": will_datetime_end > max_time,
|
|
"is_abnormal": min_time > will_datetime_start or will_datetime_end > max_time,
|
|
})
|
|
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.planning_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
|
|
|
|
# 这里需要将 浮点型时间转为 时间型
|
|
check_in_start = self.str_to_time(current_date, check_in_start)
|
|
check_in_end = self.str_to_time(current_date, check_in_end)
|
|
check_out_start = self.str_to_time(current_date, check_out_start)
|
|
check_out_end = self.str_to_time(current_date, check_out_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)
|
|
|
|
# 判断打卡情况排除未打卡情况
|
|
# 上班
|
|
attendance_detail_obj = self.env['hr.attendance.detail'].sudo().search(
|
|
[("exception_type", "!=", "未打卡"),
|
|
("userid", "=", user.oauth_uid),
|
|
("checkin_time", ">=", check_in_start),
|
|
("checkin_time", "<=", check_in_end),
|
|
])
|
|
# 关联 打卡详情
|
|
attendance_detail_obj.hr_attendance_id = attendance_obj.id
|
|
|
|
if len(attendance_detail_obj) <= 0:
|
|
attendance_obj.sudo().write({"is_no_card": True, "is_abnormal": True})
|
|
# 打了一次卡记录说明缺卡
|
|
else:
|
|
# 多次打卡时间的集合
|
|
checkin_time_list = [obj.checkin_time for obj in attendance_detail_obj]
|
|
# 最早 时间
|
|
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,
|
|
})
|
|
|
|
# 下班
|
|
attendance_detail_obj = self.env['hr.attendance.detail'].sudo().search(
|
|
[("exception_type", "!=", "未打卡"),
|
|
("userid", "=", user.oauth_uid),
|
|
("checkin_time", ">=", check_out_start),
|
|
("checkin_time", "<=", check_out_end),
|
|
])
|
|
# 关联 打卡详情
|
|
attendance_detail_obj.hr_attendance_id = attendance_obj.id
|
|
|
|
if len(attendance_detail_obj) <= 0:
|
|
attendance_obj.sudo().write({"is_no_card": True, "is_abnormal": True})
|
|
# 打了一次卡记录说明缺卡
|
|
else:
|
|
# 多次打卡时间的集合
|
|
checkin_time_list = [obj.checkin_time for obj in attendance_detail_obj]
|
|
# 最早 时间
|
|
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,
|
|
})
|
|
else:
|
|
attendance_obj.sudo().write({
|
|
"sch_check_out_time": planning_id.end_datetime,
|
|
"check_out": max_time,
|
|
"is_late_card": max_time < planning_id.end_datetime,
|
|
"is_no_card": True,
|
|
"is_abnormal": True,
|
|
})
|
|
|
|
return True
|
|
|
|
@staticmethod
|
|
def divide_time_range_fixed(start_time, end_time):
|
|
time_ranges = [start_time.strftime("%Y-%m-%d")]
|
|
while start_time <= 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: 转换后的时间
|
|
"""
|
|
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]
|