diff --git a/soong_workflow/data/soong_workflow_time.xml b/soong_workflow/data/soong_workflow_time.xml index 559916df5..fdd74a5eb 100644 --- a/soong_workflow/data/soong_workflow_time.xml +++ b/soong_workflow/data/soong_workflow_time.xml @@ -14,5 +14,18 @@ -1 + + 定时任务:待办提醒 + + code + model.to_do_process_reminders() + + + 1 + days + + -1 + + diff --git a/soong_workflow/models/soong_workflow_record.py b/soong_workflow/models/soong_workflow_record.py index 67ffa2ed4..f6f339ebd 100644 --- a/soong_workflow/models/soong_workflow_record.py +++ b/soong_workflow/models/soong_workflow_record.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from odoo import models, fields, api, _ +from odoo import models, exceptions from odoo.exceptions import ValidationError, UserError import logging from datetime import datetime, date, time, timedelta @@ -395,6 +396,55 @@ class SoongWorkflowRecord(models.Model): return super(SoongWorkflowRecord, self).get_view(view_id, view_type, **options) + def to_do_process_reminders(self): + workflow_records = self.env['soong.workflow.record'].search([ + ('approve_state', '=', 'pending'), + ('employee_ids', '!=', False) + ]) + employee_tasks = {} + for record in workflow_records: + try: + biz_record = self.env[record.res_model].browse(record.res_id) + urgency_level = biz_record.urgency_level or 'normal' + task_type = 'urgent' if urgency_level in ('urgent', 'emergency') else 'normal' + + for emp in record.employee_ids: + emp_id = emp.id + if emp_id not in employee_tasks: + employee_tasks[emp_id] = {'urgent': 0, 'normal': 0, 'employee': emp} + employee_tasks[emp_id][task_type] += 1 + except exceptions.MissingError as e: + error_msg = str(e).strip() + if "记录不存在或已删除" in error_msg: + continue + + for emp_id, task_data in employee_tasks.items(): + emp = task_data['employee'] + urgent_count = task_data['urgent'] + normal_count = task_data['normal'] + msg_parts = [] + if urgent_count > 0: + msg_parts.append(f'特急任务{urgent_count}条') + if normal_count > 0: + msg_parts.append(f'常规待办{normal_count}条') + if not msg_parts: + continue + reminder_msg = ( + f"您好,截至今日17:30,您有 {','.join(msg_parts)} 尚未处理," + "请及时登录系统完成办理。感谢配合!" + ) + menu_id = self.env['ir.ui.menu'].search([('name', '=', '待办流程')], limit=1) + target_url = f'https://oa.thtzjt.com/web#action=4378&model=soong.workflow.record&view_type=list&cids=2-1-3-4&menu_id={menu_id.id}' + url = self.sudo().env["wecom.apps"].generate_oauth_login_url(target_url, emp.user_id.id) + self.sudo().env["wecom.apps"].sync_send_message_textcard( + category='notice', + user_id=emp.wecom_userid, + title='【待办流程提醒】', + description=reminder_msg, + url=url, + btntxt="查看详情" + ) + # def get_department_employees(self, department_id): # valid_user_ids = set() # employee_ids = self.sudo().env['hr.employee'].search([('department_id', '=', department_id.id)]) diff --git a/yuthon_hr_attendance/data/attendance_month_date.xml b/yuthon_hr_attendance/data/attendance_month_date.xml index c4c7824ec..ec50ed636 100644 --- a/yuthon_hr_attendance/data/attendance_month_date.xml +++ b/yuthon_hr_attendance/data/attendance_month_date.xml @@ -1,6 +1,19 @@ + + 定时任务:考勤异常提醒 + + code + model.attendance_abnormal_remind() + + + months + 1 + + -1 + + 定时任务:考勤统计 diff --git a/yuthon_hr_attendance/models/hr_attendance.py b/yuthon_hr_attendance/models/hr_attendance.py index 2cfa9473f..8f381aa35 100644 --- a/yuthon_hr_attendance/models/hr_attendance.py +++ b/yuthon_hr_attendance/models/hr_attendance.py @@ -3,7 +3,7 @@ from datetime import datetime, time from re import search from odoo import models, fields, api, exceptions, _ -from datetime import timedelta +from datetime import timedelta, date from dateutil.relativedelta import relativedelta from odoo.tools import format_datetime, format_duration import logging @@ -242,55 +242,71 @@ class HrAttendance(models.Model): #考勤异常提醒 def attendance_abnormal_remind(self): - all_employees = self.env['hr.employee'].search([]) - current_date = fields.Date.today() - current_dt = fields.Datetime.now() - month_start = current_date.replace(day=1) - month_end = (month_start + relativedelta(months=1) - relativedelta(days=1)) - start_time = datetime.combine(current_date, datetime.min.time().replace(hour=8, minute=50)) - end_time = datetime.combine(current_date, datetime.min.time().replace(hour=9, minute=10)) - if not (start_time <= current_dt <= end_time): - return - if not self._process_shift_days(current_date): - return - for employee in all_employees: - domain = [ - ('shift_name', '=', '行政班次'), - ('employee_id', '=', employee.id), - ('current_data', '>=', month_start), - ('current_data', '<=', month_end), - ] - is_no_card = 0 - is_early_card = 0 - is_late_card = 0 - is_abnormal = 0 - absenteeism_card = 0 - attend_records = self.env['hr.attendance'].search(domain) - has_abnormal = False - for record in attend_records: - if record.is_no_card or record.is_early_card or record.is_late_card or record.is_abnormal or record.absenteeism_card: - has_abnormal = True - if record.is_no_card: - is_no_card += 1 - if record.is_early_card: - is_early_card += 1 - if record.is_late_card: - is_late_card += 1 - if record.is_abnormal: - is_abnormal += 1 - if record.absenteeism_card: - absenteeism_card += 1 - if has_abnormal: - url = f"https://oa.thtzjt.com/webh5#/clock_in_staticstis?user_id={employee.user_id.id}" - self.sudo().env["wecom.apps"].sync_send_message_textcard( - category='default', - user_id=employee.wecom_userid, - title="打卡异常提醒", - description="本月迟到: {}\n本月缺卡: {}\n本月早退: {}\n本月异常:{}\n 本月旷工: {}\n" - .format(is_late_card, is_no_card, is_early_card, is_abnormal, absenteeism_card), - url=url, - btntxt="详细信息" - ) + today = date.today() + current_year = today.year + current_month = today.month + first_day = date(current_year, current_month, 1) + next_month = current_month + 1 if current_month < 12 else 1 + next_year = current_year if current_month < 12 else current_year + 1 + last_day = date(next_year, next_month, 1) - timedelta(days=1) + + # 搜索本月的考勤记录 + attendance_records = self.env['hr.attendance'].search([ + ('current_data', '>=', first_day), + ('current_data', '<=', last_day), + ('shift_name', '!=', '休息') + ]) + # 按员工分组,整理异常记录(结构:{员工ID: [异常文案列表, 员工对象]}) + employee_abnormal = {} + for rec in attendance_records: + emp = rec.employee_id + emp_id = emp.id + if emp_id not in employee_abnormal: + employee_abnormal[emp_id] = {'abnormal_list': [], 'employee': emp} + + # 提取基础信息:日期(月-日)、班次 + att_date = rec.current_data + date_str = att_date.strftime('%m-%d') + shift_name = rec.shift_name + + # 判断异常类型(优先级:缺卡 > 迟到/早退) + abnormal_desc = "" + # 判断缺卡类型 + if not rec.check_in and not rec.check_out: + abnormal_desc = "缺上班卡、缺下班卡" + elif not rec.check_in: + abnormal_desc = "缺上班卡" + elif not rec.check_out: + abnormal_desc = "缺下班卡" + # 无缺卡时,判断迟到/早退 + else: + abnormal_list = [] + if rec.is_late_card: + abnormal_list.append("迟到") + if rec.is_early_card: + abnormal_list.append("早退") + if abnormal_list: + abnormal_desc = '、'.join(abnormal_list) + + if abnormal_desc: + single_abnormal = f"{date_str} {shift_name} {abnormal_desc}" + employee_abnormal[emp_id]['abnormal_list'].append(single_abnormal) + + for emp_id, data in employee_abnormal.items(): + emp = data['employee'] + abnormal_list = data['abnormal_list'] + if not abnormal_list: + continue + abnormal_str = '、'.join(abnormal_list) + reminder_msg = f"{abnormal_str},请尽快处理。" + menu_id = self.env['ir.ui.menu'].search([('name', '=', '我的考勤')]) + target_url = f'https://oa.thtzjt.com/web=#action=2465&model=hr.attendance&view_type=list&cids=2-1-3-4&menu_id={menu_id}' + url = self.sudo().env["wecom.apps"].generate_oauth_login_url(target_url, emp.user_id.id) + self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice', + user_id=emp.wecom_userid, + title='【考勤异常提醒】', + description=reminder_msg, + url=url, btntxt="查看详情") def missing_card_judgment(self): today = fields.Date.today() diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py index 79ab8cc95..87f35b4ce 100644 --- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py +++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py @@ -934,6 +934,7 @@ class VacationOverline(models.Model): vacation_id = fields.Many2one('yuthon.vacation.reports', string='关联假期额度记录') employee_id = fields.Many2one('hr.employee', string='员工', compute='_compute_employee_id', store=True) department_id = fields.Many2one('hr.department', related='employee_id.department_id', string="部门") + company_id = fields.Many2one('res.company', related='employee_id.company_id', string="公司") workflow_record_id = fields.Many2one('soong.workflow.record', string='流程') update_type= fields.Selection([('add', '增加'), ('reduce', '减少')], string='更新类型') workday = fields.Float(string='工作日') diff --git a/yuthon_hr_attendance/security/planning_group.xml b/yuthon_hr_attendance/security/planning_group.xml index 6d1edea1b..6bf543528 100644 --- a/yuthon_hr_attendance/security/planning_group.xml +++ b/yuthon_hr_attendance/security/planning_group.xml @@ -1,6 +1,11 @@ + + 加班单明细行多公司 + + [('company_id', 'in', company_ids)] + 考勤确认单多公司