From d6faa5c848d3559421da392f968491fdd6f69d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Tue, 29 Oct 2024 14:43:15 +0800 Subject: [PATCH 1/3] pengyuthon --- hr_attendance_ext/__init__.py | 6 - hr_attendance_ext/__manifest__.py | 31 -- hr_attendance_ext/data/hr_attendance_time.xml | 18 - hr_attendance_ext/models/__init__.py | 5 - hr_attendance_ext/models/hr_attendance.py | 342 ------------------ .../models/hr_attendance_detail.py | 318 ---------------- hr_attendance_ext/models/wx_work.py | 12 - .../security/ir.model.access.csv | 7 - hr_attendance_ext/static/description/icon.png | Bin 6008 -> 0 bytes hr_attendance_ext/utils/__init__.py | 3 - hr_attendance_ext/utils/common.py | 31 -- .../views/hr_attendance_detail_views.xml | 80 ---- .../views/hr_attendance_views.xml | 55 --- hr_attendance_ext/views/wx_work_views.xml | 13 - 14 files changed, 921 deletions(-) delete mode 100644 hr_attendance_ext/__init__.py delete mode 100644 hr_attendance_ext/__manifest__.py delete mode 100644 hr_attendance_ext/data/hr_attendance_time.xml delete mode 100644 hr_attendance_ext/models/__init__.py delete mode 100644 hr_attendance_ext/models/hr_attendance.py delete mode 100644 hr_attendance_ext/models/hr_attendance_detail.py delete mode 100644 hr_attendance_ext/models/wx_work.py delete mode 100644 hr_attendance_ext/security/ir.model.access.csv delete mode 100644 hr_attendance_ext/static/description/icon.png delete mode 100644 hr_attendance_ext/utils/__init__.py delete mode 100644 hr_attendance_ext/utils/common.py delete mode 100644 hr_attendance_ext/views/hr_attendance_detail_views.xml delete mode 100644 hr_attendance_ext/views/hr_attendance_views.xml delete mode 100644 hr_attendance_ext/views/wx_work_views.xml diff --git a/hr_attendance_ext/__init__.py b/hr_attendance_ext/__init__.py deleted file mode 100644 index 80275e612..000000000 --- a/hr_attendance_ext/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import models -from . import utils - - diff --git a/hr_attendance_ext/__manifest__.py b/hr_attendance_ext/__manifest__.py deleted file mode 100644 index 9cfa8a9e3..000000000 --- a/hr_attendance_ext/__manifest__.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -{ - 'name': '出勤拓展', - 'version': '17.0.0.1', - 'category': '出勤拓展', - 'author': 'pengyuthon->ljm', - 'sequence': 190, - 'description': """ -功能介绍 -============== - -* 新增 定时任务 和 抓取企业微信的出勤数据的核心模块 -* 新增 企业微信出勤记录的原始数据 -* 新增 企业微信出勤记录和出勤模块的融合数据 -* 新增 月度汇总明细结果表 -* 新增 月度汇总明细表,附加权限 -* 新增 月度汇总向导,年份维护表 -* 新增 员工出勤以及判断是否应该出勤的逻辑等 - """, - 'depends': ['hr_attendance', 'yuthon_wechat'], - 'data': [ - 'security/ir.model.access.csv', - 'data/hr_attendance_time.xml', - 'views/hr_attendance_views.xml', - 'views/hr_attendance_detail_views.xml', - 'views/wx_work_views.xml', - ], - 'application': True, - 'installable': True, - 'auto_install': False, -} diff --git a/hr_attendance_ext/data/hr_attendance_time.xml b/hr_attendance_ext/data/hr_attendance_time.xml deleted file mode 100644 index 08ec7083c..000000000 --- a/hr_attendance_ext/data/hr_attendance_time.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - 定时任务:定时抓取上下班打卡信息 - - code - model.get_hr_attendance_time() - - - 120 - months - - -1 - - - - \ No newline at end of file diff --git a/hr_attendance_ext/models/__init__.py b/hr_attendance_ext/models/__init__.py deleted file mode 100644 index 8bfa2eeed..000000000 --- a/hr_attendance_ext/models/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import hr_attendance -from . import hr_attendance_detail -from . import wx_work diff --git a/hr_attendance_ext/models/hr_attendance.py b/hr_attendance_ext/models/hr_attendance.py deleted file mode 100644 index a490ecb1a..000000000 --- a/hr_attendance_ext/models/hr_attendance.py +++ /dev/null @@ -1,342 +0,0 @@ -# -*- coding: utf-8 -*- -import pytz -from datetime import datetime, timedelta -from odoo import api, fields, models, tools, _ - - -class HrAttendance(models.Model): - _inherit = "hr.attendance" - _order = 'current_data desc, check_in desc' - - userid = fields.Char(string='userid') - groupname = fields.Char(string='打卡规则名称') - current_data = fields.Date(string='打卡日期', tracking=True) - sch_check_in_time = fields.Datetime(string='上班正常时间') - sch_check_out_time = fields.Datetime(string='下班正常时间') - sch_check_noon_time = fields.Datetime(string='中午正常时间') - check_noon = fields.Datetime(string='中午打卡', tracking=True) - check_in = fields.Datetime(string="上班打卡", required=False, default=False, tracking=True) - check_out = fields.Datetime(string="下班打卡", tracking=True) - attendance_type = fields.Selection([('wx_work', '企业微信')], readonly=True, string='来源') - # field_work_check = fields.Datetime(string="外勤打卡") - - attendance_detail_ids = fields.One2many(string="打卡详情", comodel_name='hr.attendance.detail', inverse_name='hr_attendance_id') - replacement_approval_ids = fields.One2many(string="补卡详情", comodel_name='approval2.request', compute='_compute_replacement_approval_ids') - go_out_approval_ids = fields.One2many(string="外出详情", comodel_name='approval2.request', compute='_compute_go_out_approval_ids') - work_overtime_approval_ids = fields.One2many(string="加班详情", comodel_name='approval2.request', compute='_compute_work_overtime_approval_ids') - hr_leave_approval_ids = fields.One2many(string="请假详情", comodel_name='hr.leave', compute='_compute_hr_leave_approval_ids') - - def action_show_details(self): - self.ensure_one() - view = self.env.ref("hr_attendance_ext.view_hr_attendance_detail_tree") - form_view = self.env.ref("hr_attendance_ext.view_hr_attendance_detail_form") - return { - "name": _("出勤详情"), - "type": "ir.actions.act_window", - "view_mode": "tree,form", - "res_model": "hr.attendance.detail", - "views": [(view.id, "tree"), (form_view.id, "form")], - "view_id": view.id, - "target": "current", - "res_id": self.id, - "domain": [('hr_attendance_id', '=', self.id)], - "context": {'hr_attendance_id': self.id} - } - - def replacement_approval_action_show_details(self): - self.ensure_one() - view = self.env.ref("approvals2.approval_request_view_tree") - form_view = self.env.ref("approvals2.approval_request_view_form") - if self.current_data: - approval_request = self.env["approval2.request"].sudo().search([ - ("request_owner_id", "=", self.employee_id.user_id.id), - ("approvals_request_type", "=", "card_replacement"), - ("replacement_date", "=", self.current_data), - # ("state", "=", "approved") - ]) - else: - approval_request = self.env["approval2.request"].sudo().search([('id', '<', 0)]) - return { - "name": _("补卡详情"), - "type": "ir.actions.act_window", - "view_mode": "tree,form", - "res_model": "approval2.request", - "views": [(view.id, "tree"), (form_view.id, "form")], - "view_id": view.id, - "target": "current", - "res_id": self.id, - "domain": [('id', 'in', approval_request.ids)], - "context": {'id': approval_request.ids} - } - - def go_out_approval_action_show_details(self): - self.ensure_one() - view = self.env.ref("approvals2.approval_request_view_tree") - form_view = self.env.ref("approvals2.approval_request_view_form") - if self.current_data: - date_start = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8) - date_end = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8) - - approval_request = self.env["approval2.request"].sudo().search([ - ("request_owner_id", "=", self.employee_id.user_id.id), - ("approvals_request_type", "=", "go_out_approval"), - ("approval_end_time", ">=", date_start), - ("approval_start_time", "<=", date_end), - # ("state", "=", "approved") - ]) - else: - approval_request = self.env["approval2.request"].sudo().search([('id', '<', 0)]) - - return { - "name": _("外出详情"), - "type": "ir.actions.act_window", - "view_mode": "tree,form", - "res_model": "approval2.request", - "views": [(view.id, "tree"), (form_view.id, "form")], - "view_id": view.id, - "target": "current", - "res_id": self.id, - "domain": [('id', 'in', approval_request.ids)], - "context": {'id': approval_request.ids} - } - - def work_overtime_action_show_details(self): - self.ensure_one() - view = self.env.ref("approvals2.approval_request_view_tree") - form_view = self.env.ref("approvals2.approval_request_view_form") - if self.current_data: - date_start = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8) - date_end = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8) - - approval_request = self.env["approval2.request"].sudo().search([ - ("request_owner_id", "=", self.employee_id.user_id.id), - ("approvals_request_type", "=", "work_overtime"), - ("approval_end_time", ">=", date_start), - ("approval_start_time", "<=", date_end), - # ("state", "=", "approved") - ]) - else: - approval_request = self.env["approval2.request"].sudo().search([('id', '<', 0)]) - return { - "name": _("加班详情"), - "type": "ir.actions.act_window", - "view_mode": "tree,form", - "res_model": "approval2.request", - "views": [(view.id, "tree"), (form_view.id, "form")], - "view_id": view.id, - "target": "current", - "res_id": self.id, - "domain": [('id', 'in', approval_request.ids)], - "context": {'id': approval_request.ids} - } - - def hr_leave_action_show_details(self): - self.ensure_one() - view = self.env.ref("hr_holidays.hr_leave_view_tree_my") - form_view = self.env.ref("hr_holidays.hr_leave_view_form") - if self.current_data: - date_start = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8) - date_end = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8) - - approval_request = self.env["hr.leave"].sudo().search([ - ('employee_id', '=', self.employee_id.id), - ('date_from', '<=', date_end), - ('date_to', '>=', date_start), - # ("state", "=", "validate") - ]) - else: - approval_request = self.env["hr.leave"].sudo().search([('id', '<', 0)]) - - return { - "name": _("请假详情"), - "type": "ir.actions.act_window", - "view_mode": "tree,form", - "res_model": "hr.leave", - "views": [(view.id, "tree"), (form_view.id, "form")], - "view_id": view.id, - "target": "current", - "res_id": self.id, - "domain": [('id', 'in', approval_request.ids)], - "context": {'id': approval_request.ids, 'short_name': 1, 'search_default_active_time_off': 1} - } - - @api.constrains('check_in', 'check_out', 'employee_id') - def _check_validity(self): - for attendance in self: - if attendance.attendance_type != 'wx_work': - super(HrAttendance, attendance)._check_validity() - - # 审批补卡详情 approval2 - def _compute_replacement_approval_ids(self): - for record in self: - approval_request = self.env["approval2.request"].sudo().search([ - ("request_owner_id", "=", record.employee_id.user_id.id), - ("approvals_request_type", "=", "card_replacement"), - ("replacement_date", "=", record.current_data), - # ("state", "=", "approved") - ]) - record.replacement_approval_ids = approval_request.ids if approval_request.ids else None - - # 审批请假详情 hr.leave - def _compute_hr_leave_approval_ids(self): - for record in self: - record.hr_leave_approval_ids = None - if record.current_data: - date_start = fields.Datetime.from_string( - fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8) - date_end = fields.Datetime.from_string( - fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8) - - approval_request = self.env["hr.leave"].sudo().search([ - ('employee_id', '=', record.employee_id.id), - ('date_from', '<=', date_end), - ('date_to', '>=', date_start), - # ("state", "=", "validate") - ]) - record.hr_leave_approval_ids = approval_request.ids if approval_request.ids else None - - # 审批外出详情 approval2 - def _compute_go_out_approval_ids(self): - for record in self: - record.go_out_approval_ids = None - if record.current_data: - date_start = fields.Datetime.from_string(fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8) - date_end = fields.Datetime.from_string(fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8) - - approval_request = self.env["approval2.request"].sudo().search([ - ("request_owner_id", "=", record.employee_id.user_id.id), - ("approvals_request_type", "=", "go_out_approval"), - ("approval_end_time", ">=", date_start), - ("approval_start_time", "<=", date_end), - # ("state", "=", "approved") - ]) - record.go_out_approval_ids = approval_request.ids if approval_request.ids else None - - # 审批加班详情 approval2 - def _compute_work_overtime_approval_ids(self): - for record in self: - record.work_overtime_approval_ids = None - if record.current_data: - date_start = fields.Datetime.from_string( - fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8) - date_end = fields.Datetime.from_string( - fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8) - - approval_request = self.env["approval2.request"].sudo().search([ - ("request_owner_id", "=", record.employee_id.user_id.id), - ("approvals_request_type", "=", "work_overtime"), - ("approval_end_time", ">=", date_start), - ("approval_start_time", "<=", date_end), - # ("state", "=", "approved") - ]) - record.work_overtime_approval_ids = approval_request.ids if approval_request.ids else None - - 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 - - @api.depends('check_in', 'sch_check_in_time') - def is_come_late(self): - self.ensure_one() - if self.check_in and self.sch_check_in_time and self.check_in > self.sch_check_in_time: - return True - return False - - 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 - # 是不是工作时间 - attendance_data = resource_calendar.attendance_ids.filtered(lambda s: s.dayofweek == str(current_week)) - 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_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 - - @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 - - @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 diff --git a/hr_attendance_ext/models/hr_attendance_detail.py b/hr_attendance_ext/models/hr_attendance_detail.py deleted file mode 100644 index 6eba2507b..000000000 --- a/hr_attendance_ext/models/hr_attendance_detail.py +++ /dev/null @@ -1,318 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import base64 -import datetime -import logging -import traceback -from odoo import api, fields, models, tools, _ -from ...hr_attendance_ext.utils.common import utils_list_page - -_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("hr_attendance_ext.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): - """ - 获取企业微信的打卡信息 - :param wx_work_checkin_api: 企业微信api - :param userid_limit: 用户列表 - :return: - """ - # 先按照天抓取,时间戳 - now_time = datetime.datetime.now() + datetime.timedelta(hours=8) - start_time = now_time - datetime.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.datetime.fromtimestamp(checkin_time_stamp) if checkin_time_stamp else None - sch_checkin_time = datetime.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 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()))) - - userid = data.get("userid", "") - # 区分当天的上班,下班,中午打卡,合并一个 - checkin_time_now = checkin_time + datetime.timedelta(hours=8) if checkin_time else None - sch_checkin_time_now = sch_checkin_time + datetime.timedelta(hours=8) if sch_checkin_time else None - current_data = (sch_checkin_time + datetime.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) - 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) - attendance_obj = self.env['hr.attendance'].sudo().create({ - 'employee_id': hr_employee_obj.id, - "userid": userid, - "current_data": current_data, - "attendance_type": "wx_work" - }) - # 赋值企业微信打卡数据 - attendance_obj.attendance_type = "wx_work" - - checkin_type = data.get("checkin_type", "") - exception_type = data.get("exception_type", "") - if sch_checkin_time_now and str(sch_checkin_time_now.strftime("%H")) == "13" and checkin_type == "上班打卡" and exception_type != "未打卡": - attendance_obj.sudo().write({ - "sch_check_noon_time": sch_checkin_time, - "check_noon": checkin_time - }) - elif checkin_type == "上班打卡" and exception_type != "未打卡": - attendance_obj.sudo().write({ - "sch_check_in_time": sch_checkin_time, - "check_in": checkin_time - }) - elif checkin_type == "下班打卡" and exception_type != "未打卡": - attendance_obj.sudo().write({ - "sch_check_out_time": sch_checkin_time, - "check_out": checkin_time - }) - # elif checkin_type == "外出打卡": - # attendance_obj.sudo().write({ - # "field_work_check": checkin_time, - # }) - # 进行关联 - 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() - # 企业微信获取打卡数据,要求用户分组100,分批获取打卡记录 - for userid_limit in utils_list_page(user_list, 100): - # 获取企业微信的打卡信息 - try: - attendance_data = self._get_wx_attendance_data(userid_limit) - 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()))) - return True - - diff --git a/hr_attendance_ext/models/wx_work.py b/hr_attendance_ext/models/wx_work.py deleted file mode 100644 index 24dead8d8..000000000 --- a/hr_attendance_ext/models/wx_work.py +++ /dev/null @@ -1,12 +0,0 @@ -import logging -import json -from datetime import datetime, timedelta -from odoo import models, fields - -_logger = logging.getLogger(__name__) - - -class AuthOAuthProvider(models.Model): - _inherit = "auth.oauth.provider" - - wx_work_image_path = fields.Char(string="企业微信图片保存地址") diff --git a/hr_attendance_ext/security/ir.model.access.csv b/hr_attendance_ext/security/ir.model.access.csv deleted file mode 100644 index cf875294b..000000000 --- a/hr_attendance_ext/security/ir.model.access.csv +++ /dev/null @@ -1,7 +0,0 @@ -id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink - -access_hr_attendance_detail_user,hr.attendance.detail.user,model_hr_attendance_detail,base.group_user,1,0,0,0 -access_hr_attendance_detail_manager,hr.attendance.detail.manager,model_hr_attendance_detail,base.group_user,1,1,1,1 - -access_hr_attendance_detail_image_user,hr.attendance.detail.image.user,model_hr_attendance_detail_image,base.group_user,1,0,0,0 -access_hr_attendance_detail_image_manager,hr.attendance.detail.image.manager,model_hr_attendance_detail_image,base.group_user,1,1,1,1 diff --git a/hr_attendance_ext/static/description/icon.png b/hr_attendance_ext/static/description/icon.png deleted file mode 100644 index 5cca7ac8bde34efb99dff9f796b7616d67af0e79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6008 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 diff --git a/hr_attendance_ext/utils/__init__.py b/hr_attendance_ext/utils/__init__.py deleted file mode 100644 index aa9c3f5d9..000000000 --- a/hr_attendance_ext/utils/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import common diff --git a/hr_attendance_ext/utils/common.py b/hr_attendance_ext/utils/common.py deleted file mode 100644 index c58026e43..000000000 --- a/hr_attendance_ext/utils/common.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -import os - -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 os_path_join(path, filename): - """ - 默认传入的是debian的路径 /xx/xx/xx/转换成多系统兼容 - :param path: - :param filename: - :return: - """ - image_path_list = path.split('/') - image_path = "" - for path in image_path_list: - image_path = os.path.join(str(image_path), str(path)) - image = os.path.join(image_path, filename) - return image diff --git a/hr_attendance_ext/views/hr_attendance_detail_views.xml b/hr_attendance_ext/views/hr_attendance_detail_views.xml deleted file mode 100644 index f1c3b70f6..000000000 --- a/hr_attendance_ext/views/hr_attendance_detail_views.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - hr.attendance.detail.image.tree - hr.attendance.detail.image - - - - - - - - - - hr.attendance.detail.image.form - hr.attendance.detail.image - -
- - - - - - -
-
-
- - - hr.attendance.detail.tree - hr.attendance.detail - - - - - - - - - - - - -