From 8a74a51567ab8e23900191105e7a897ede74632d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E5=81=A5=E6=98=8E?= Date: Mon, 28 Oct 2024 21:10:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E8=81=8A?= =?UTF-8?q?=E5=A4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_wechat/__init__.py | 2 +- yuthon_wechat/__manifest__.py | 6 +- yuthon_wechat/data/hr_attendance_time.xml | 18 + yuthon_wechat/models/__init__.py | 4 +- yuthon_wechat/models/hr_attendance.py | 161 +++++++++ yuthon_wechat/models/hr_attendance_detail.py | 330 ++++++++++++++++++ yuthon_wechat/models/wx_work.py | 12 + yuthon_wechat/security/ir.model.access.csv | 6 + yuthon_wechat/utils/__init__.py | 3 + yuthon_wechat/utils/common.py | 31 ++ .../views/hr_attendance_detail_views.xml | 80 +++++ yuthon_wechat/views/hr_attendance_views.xml | 55 +++ yuthon_wechat/views/wx_work_views.xml | 13 + 13 files changed, 718 insertions(+), 3 deletions(-) create mode 100644 yuthon_wechat/data/hr_attendance_time.xml create mode 100644 yuthon_wechat/models/hr_attendance.py create mode 100644 yuthon_wechat/models/hr_attendance_detail.py create mode 100644 yuthon_wechat/models/wx_work.py create mode 100644 yuthon_wechat/utils/__init__.py create mode 100644 yuthon_wechat/utils/common.py create mode 100644 yuthon_wechat/views/hr_attendance_detail_views.xml create mode 100644 yuthon_wechat/views/hr_attendance_views.xml create mode 100644 yuthon_wechat/views/wx_work_views.xml diff --git a/yuthon_wechat/__init__.py b/yuthon_wechat/__init__.py index 20b498afe..b276f2f3f 100644 --- a/yuthon_wechat/__init__.py +++ b/yuthon_wechat/__init__.py @@ -3,4 +3,4 @@ from . import models from . import controllers from . import wizard - +from . import utils diff --git a/yuthon_wechat/__manifest__.py b/yuthon_wechat/__manifest__.py index 48a48193c..7ce5d0996 100644 --- a/yuthon_wechat/__manifest__.py +++ b/yuthon_wechat/__manifest__.py @@ -18,6 +18,7 @@ 'depends': ['base', 'auth_oauth', 'hr'], 'data': [ 'security/ir.model.access.csv', + 'data/hr_attendance_time.xml', # 'data/wecom_apps_cron_data.xml', 'data/wecom_api_data.xml', 'data/auth_oauth_provider.xml', @@ -25,7 +26,10 @@ 'views/wecom_apps_views.xml', 'views/hr_employee_views.xml', 'views/wecom_apps_config_views.xml', - 'wizard/wecom_api_sync_wizard.xml' + 'wizard/wecom_api_sync_wizard.xml', + 'views/hr_attendance_views.xml', + 'views/hr_attendance_detail_views.xml', + 'views/wx_work_views.xml', ], 'application': True, 'installable': True, diff --git a/yuthon_wechat/data/hr_attendance_time.xml b/yuthon_wechat/data/hr_attendance_time.xml new file mode 100644 index 000000000..08ec7083c --- /dev/null +++ b/yuthon_wechat/data/hr_attendance_time.xml @@ -0,0 +1,18 @@ + + + + + 定时任务:定时抓取上下班打卡信息 + + code + model.get_hr_attendance_time() + + + 120 + months + + -1 + + + + \ No newline at end of file diff --git a/yuthon_wechat/models/__init__.py b/yuthon_wechat/models/__init__.py index 3e861e4bd..8412b8fe6 100644 --- a/yuthon_wechat/models/__init__.py +++ b/yuthon_wechat/models/__init__.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- - from . import wecom_api from . import wecom_apps from . import hr_employee from . import res_users from . import wecom_apps_config +from . import hr_attendance_detail +from . import wx_work +from . import hr_attendance diff --git a/yuthon_wechat/models/hr_attendance.py b/yuthon_wechat/models/hr_attendance.py new file mode 100644 index 000000000..9734d871a --- /dev/null +++ b/yuthon_wechat/models/hr_attendance.py @@ -0,0 +1,161 @@ +# -*- 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("yuthon_wechat.view_hr_attendance_detail_tree") + form_view = self.env.ref("yuthon_wechat.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} + } + + @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() + + + 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/yuthon_wechat/models/hr_attendance_detail.py b/yuthon_wechat/models/hr_attendance_detail.py new file mode 100644 index 000000000..bc93dd34c --- /dev/null +++ b/yuthon_wechat/models/hr_attendance_detail.py @@ -0,0 +1,330 @@ +# -*- coding: utf-8 -*- +import os +import base64 +import datetime +import logging +import traceback +from odoo import api, fields, models, tools, _ +from ...yuthon_wechat.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("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): + """ + 获取企业微信的打卡信息 + :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 exception_type != "未打卡": + if checkin_time < attendance_obj.check_in: + attendance_obj.sudo().write({ + "sch_check_in_time": sch_checkin_time, + "check_in": checkin_time + }) + if checkin_time > attendance_obj.check_out: + attendance_obj.sudo().write({ + "sch_check_out_time": sch_checkin_time, + "check_out": checkin_time + }) + # 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/yuthon_wechat/models/wx_work.py b/yuthon_wechat/models/wx_work.py new file mode 100644 index 000000000..24dead8d8 --- /dev/null +++ b/yuthon_wechat/models/wx_work.py @@ -0,0 +1,12 @@ +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/yuthon_wechat/security/ir.model.access.csv b/yuthon_wechat/security/ir.model.access.csv index 9f6991423..bb47359ec 100644 --- a/yuthon_wechat/security/ir.model.access.csv +++ b/yuthon_wechat/security/ir.model.access.csv @@ -4,3 +4,9 @@ access_wecom_apps,wecom.apps.user,model_wecom_apps,,1,1,1,1 access__wecom_apps_config,wecom.app.config.user,model_wecom_apps_config,,1,1,1,1 access_wecom_api_sync_wizard_manager,wecom.api.sync.wizard,model_wecom_api_sync_wizard,,1,1,1,1 + +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/yuthon_wechat/utils/__init__.py b/yuthon_wechat/utils/__init__.py new file mode 100644 index 000000000..aa9c3f5d9 --- /dev/null +++ b/yuthon_wechat/utils/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import common diff --git a/yuthon_wechat/utils/common.py b/yuthon_wechat/utils/common.py new file mode 100644 index 000000000..c58026e43 --- /dev/null +++ b/yuthon_wechat/utils/common.py @@ -0,0 +1,31 @@ +# -*- 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/yuthon_wechat/views/hr_attendance_detail_views.xml b/yuthon_wechat/views/hr_attendance_detail_views.xml new file mode 100644 index 000000000..f1c3b70f6 --- /dev/null +++ b/yuthon_wechat/views/hr_attendance_detail_views.xml @@ -0,0 +1,80 @@ + + + + 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 + + + + + + + + + + + + +