1348 lines
63 KiB
Python
1348 lines
63 KiB
Python
# -*- coding: utf-8 -*-
|
||
from datetime import datetime, time
|
||
from re import search
|
||
|
||
from odoo import models, fields, api, exceptions, _
|
||
from datetime import timedelta, date
|
||
from dateutil.relativedelta import relativedelta
|
||
from odoo.tools import format_datetime, format_duration
|
||
import logging
|
||
from odoo.exceptions import UserError, ValidationError
|
||
import calendar
|
||
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
|
||
class HrAttendance(models.Model):
|
||
_inherit = "hr.attendance"
|
||
_description = "Attendance"
|
||
_rec_name = 'current_data'
|
||
|
||
is_clock_card = fields.Boolean(string="外勤")
|
||
is_no_card = fields.Boolean(string="缺卡", group_operator='count_true')
|
||
is_early_card = fields.Boolean(string="早退", group_operator='count_true')
|
||
is_late_card = fields.Boolean(string="迟到", group_operator='count_true')
|
||
is_abnormal = fields.Boolean(string="异常", group_operator='count_true')
|
||
is_expatriation = fields.Boolean(string="外派", group_operator='count_true')
|
||
is_approval_no = fields.Boolean()
|
||
is_approval_early = fields.Boolean()
|
||
is_approval_late = fields.Boolean()
|
||
is_approval_abnormal = fields.Boolean()
|
||
user_id = fields.Many2one(related='employee_id.user_id', store=True)
|
||
absenteeism_card = fields.Boolean(string="旷工", group_operator='count_true')
|
||
is_approval_abs = fields.Boolean()
|
||
work_time_type = fields.Selection([('close_day', '休息日'), ('work_day', '工作日')], string="工作时间类型")
|
||
company_id = fields.Many2one(related='employee_id.company_id', string='公司', index=True, store=True)
|
||
on_card_location = fields.Char(string="上班打卡地点")
|
||
down_card_location = fields.Char(string="下班打卡地点")
|
||
out_card_time = fields.Datetime(string="外出打卡时间")
|
||
out_card_location = fields.Char(string="外出打卡地点")
|
||
department_id = fields.Many2one(related='employee_id.department_id', string="部门", store=True)
|
||
work_hours = fields.Float(string="工作时长")
|
||
current_data = fields.Date(string='打卡日期', tracking=True)
|
||
week = fields.Selection([('one', '一'), ('two', '二'), ('three', '三'),
|
||
('four', '四'), ('five', '五'), ('six', '六'),
|
||
('seven', '日')], string='星期', compute='_compute_week', store=True)
|
||
abnormal = fields.Integer(string="异常计算")
|
||
shift_id = fields.Many2one('planning.slot', string="班次", store=True)
|
||
shift_name = fields.Char(string="班次", store=True)
|
||
is_week = fields.Boolean(string="是否为周末")
|
||
is_holiday = fields.Boolean(string="是否为节假日")
|
||
noon_duty = fields.Datetime(string="中值")
|
||
night_duty = fields.Datetime(string="晚值")
|
||
is_approval = fields.Boolean(string="是否手动修正", default=False)
|
||
is_check_in_red = fields.Boolean(string="是否上班打卡异常")
|
||
is_check_out_red = fields.Boolean(string="是否下班打卡异常")
|
||
is_warning = fields.Boolean(compute='_compute_is_warning', string="警告", store=True)
|
||
|
||
approval_check_in = fields.Datetime(string="修正前上班打卡时间")
|
||
approval_check_out = fields.Datetime(string="修正前下班打卡时间")
|
||
is_workflow = fields.Boolean(string="是否是流程", default=False)
|
||
workflow_id = fields.Many2one('soong.workflow.record', string="流程")
|
||
is_month = fields.Boolean(string="是否为本月数据", compute='_compute_month_status', store=True)
|
||
is_last_month = fields.Boolean(string="是否为上月数据", compute='_compute_month_status', store=True)
|
||
correct_result = fields.Text(string="修正原因")
|
||
|
||
def update_workflow_id(self):
|
||
attendance_ids = self.env['hr.attendance'].search([('workflow_id', '=', True)])
|
||
for attendance in attendance_ids:
|
||
attendance.is_no_card = False
|
||
attendance.is_early_card = False
|
||
attendance.is_late_card = False
|
||
attendance.is_abnormal = False
|
||
attendance.absenteeism_card = False
|
||
|
||
@api.constrains("check_in")
|
||
def _check_in_out(self):
|
||
reminder_id = self.env['message.reminder.tasks'].search([('name', '=', self.employee_id.name), ('state', '=', 'todo'),
|
||
('type', '=', 'work'),
|
||
('current_data', '=', self.current_data)], limit=1)
|
||
if reminder_id:
|
||
reminder_id.state = 'done'
|
||
|
||
@api.constrains("check_out")
|
||
def _check_out_in(self):
|
||
if self.shift_name in ['夜班', '旧晚(旧区府晚班)']:
|
||
current_data = self.current_data + timedelta(days=1)
|
||
else:
|
||
current_data = self.current_data
|
||
reminder_id = self.env['message.reminder.tasks'].search([('name', '=', self.employee_id.name), ('state', '=', 'todo'),
|
||
('type', '=', 'offWork'),
|
||
('current_data', '=', current_data)], limit=1)
|
||
if reminder_id:
|
||
reminder_id.state = 'done'
|
||
|
||
def one_time(self):
|
||
for att in self.env["hr.attendance"].browse(self._context.get('active_ids', self._context.get('active_id'))):
|
||
if att.current_data:
|
||
att.current_data_month = f'{att.current_data.year}-{att.current_data.month}'
|
||
|
||
def task_remind(self):
|
||
all_employees = self.env['hr.employee'].search([('name', 'in', ['Administrator', '叶华锋'])])
|
||
for employee in all_employees:
|
||
self.sudo().env["wecom.apps"].sync_send_message_textcard(
|
||
category='att', user_id=employee.wecom_userid,
|
||
description="记得打卡", title="测试提醒",btntxt="详细信息", )
|
||
|
||
def create_attendance_data_holiday(self):
|
||
"""
|
||
定时任务create_attendance_data_holiday
|
||
:return:
|
||
"""
|
||
today = fields.Date.today() + timedelta(hours=8)
|
||
today_str = fields.Date.to_string(today)
|
||
existing_employees = self.env['hr.employee'].search([
|
||
('name', 'not in', ['Administrator'])
|
||
])
|
||
existing_records = self.search([
|
||
('employee_id', 'in', existing_employees.ids),
|
||
('current_data', '=', today_str)
|
||
])
|
||
existing_employee_ids = set()
|
||
for record in existing_records:
|
||
existing_employee_ids.add(record.employee_id.id)
|
||
for employee in existing_employees:
|
||
if employee.id not in existing_employee_ids:
|
||
att = self.create({
|
||
'employee_id': employee.id,
|
||
'current_data': today_str,
|
||
'check_in': False,
|
||
'check_out': False,
|
||
'is_no_card': False,
|
||
'is_abnormal': False
|
||
})
|
||
att.compute_attend()
|
||
|
||
def state_fix(self):
|
||
for att in self.env["hr.attendance"].browse(self._context.get('active_ids', self._context.get('active_id'))):
|
||
att.write({'is_no_card': False,
|
||
'is_early_card': False,
|
||
'is_late_card': False,
|
||
'is_abnormal': False,
|
||
'is_warning': False,
|
||
'absenteeism_card': False})
|
||
|
||
@api.depends('is_no_card', 'is_early_card', 'is_late_card', 'is_abnormal', 'absenteeism_card')
|
||
def _compute_is_warning(self):
|
||
for record in self:
|
||
record.is_warning = (
|
||
record.is_no_card or
|
||
record.is_early_card or
|
||
record.is_late_card or
|
||
record.is_abnormal or
|
||
record.absenteeism_card
|
||
)
|
||
|
||
def button_workflow(self):
|
||
if self.workflow_id:
|
||
return {
|
||
"name": "流程详情",
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': f'{self.workflow_id.res_model}',
|
||
'res_id': self.workflow_id.res_id,
|
||
'view_mode': 'form',
|
||
}
|
||
|
||
def compute_attendance_ids(self):
|
||
for att in self.env["hr.attendance"].browse(self._context.get('active_ids', self._context.get('active_id'))):
|
||
if att.employee_id.active == False:
|
||
att.write({
|
||
'is_early_card': False,
|
||
'is_late_card': False,
|
||
'is_abnormal': False,
|
||
'is_no_card': False,
|
||
'absenteeism_card': False,
|
||
'is_warning': False,
|
||
})
|
||
else:
|
||
att.compute_attend()
|
||
|
||
def compute_attend(self):
|
||
current_data = fields.Datetime.to_datetime(self.current_data) + timedelta(hours=8)
|
||
self.env["hr.attendance.detail"].compute_attendance_detail(self.employee_id, current_data.date(), is_manual=True)
|
||
|
||
@api.depends('current_data')
|
||
def _compute_month_status(self):
|
||
today = fields.Date.today()
|
||
# 获取本月第一天和最后一天
|
||
first_day_this_month = today.replace(day=1)
|
||
last_day_this_month = today.replace(day=calendar.monthrange(today.year, today.month)[1])
|
||
# 获取上月第一天和最后一天
|
||
first_day_last_month = first_day_this_month - relativedelta(months=1)
|
||
last_day_last_month = first_day_this_month - relativedelta(days=1)
|
||
for record in self:
|
||
if not record.current_data:
|
||
record.is_month = False
|
||
record.is_last_month = False
|
||
continue
|
||
# 判断是否为本月
|
||
record.is_month = (first_day_this_month <= record.current_data <= last_day_this_month)
|
||
# 判断是否为上月
|
||
record.is_last_month = (first_day_last_month <= record.current_data <= last_day_last_month)
|
||
|
||
def update_att(self):
|
||
valid_employees = self.sudo().env['hr.employee'].search([
|
||
('name', '=', '廖志强'),
|
||
])
|
||
# 2. 定义日期范围(2025-11-01 至 2025-11-11)
|
||
start_date = fields.Date.from_string("2025-12-31")
|
||
end_date = fields.Date.from_string("2025-01-01")
|
||
current_date = start_date
|
||
while current_date <= end_date:
|
||
for employee in valid_employees:
|
||
existing_attendance = self.env["hr.attendance"].search([
|
||
('employee_id', '=', employee.id),
|
||
('current_data', '=', current_date)
|
||
], limit=1)
|
||
if not existing_attendance:
|
||
self.env["hr.attendance.detail"].compute_attendance_detail(
|
||
employee,
|
||
current_date,
|
||
is_manual=True
|
||
)
|
||
current_date += timedelta(days=1)
|
||
|
||
@api.constrains('check_in', 'check_out')
|
||
def _check_validity_check_in_check_out(self):
|
||
""" verifies if check_in is earlier than check_out. """
|
||
for attendance in self:
|
||
if attendance.check_in and attendance.check_out:
|
||
if attendance.check_out < attendance.check_in:
|
||
print('------------------------------修改源码后期优化')
|
||
# raise exceptions.ValidationError(_('"Check Out" time cannot be earlier than "Check In" time.'))
|
||
|
||
@api.constrains('check_in', 'check_out', 'employee_id')
|
||
def _check_validity(self):
|
||
"""重写验证方法,移除未签退记录的检查"""
|
||
for attendance in self:
|
||
last_attendance_before_check_in = self.env['hr.attendance'].search([
|
||
('employee_id', '=', attendance.employee_id.id),
|
||
('check_in', '<=', attendance.check_in),
|
||
('id', '!=', attendance.id),
|
||
], order='check_in desc', limit=1)
|
||
|
||
# if last_attendance_before_check_in and last_attendance_before_check_in.check_out and last_attendance_before_check_in.check_out > attendance.check_in:
|
||
# raise exceptions.ValidationError(_(
|
||
# "Cannot create new attendance record for %(empl_name)s, the employee was already checked in on %(datetime)s",
|
||
# empl_name=attendance.employee_id.name,
|
||
# datetime=format_datetime(self.env, attendance.check_in, dt_format=False)
|
||
# ))
|
||
# if attendance.check_out:
|
||
# last_attendance_before_check_out = self.env['hr.attendance'].search([
|
||
# ('employee_id', '=', attendance.employee_id.id),
|
||
# ('check_in', '<', attendance.check_out),
|
||
# ('id', '!=', attendance.id),
|
||
# ], order='check_in desc', limit=1)
|
||
# if last_attendance_before_check_out and last_attendance_before_check_in != last_attendance_before_check_out:
|
||
# raise exceptions.ValidationError(_(
|
||
# "Cannot create new attendance record for %(empl_name)s, the employee was already checked in on %(datetime)s",
|
||
# empl_name=attendance.employee_id.name,
|
||
# datetime=format_datetime(self.env, last_attendance_before_check_out.check_in, dt_format=False)
|
||
# ))
|
||
|
||
def attendance_abnormal_remind(self):
|
||
current_utc_time = fields.Datetime.now()
|
||
target_time = current_utc_time + timedelta(hours=8)
|
||
|
||
target_hour = target_time.hour
|
||
target_minute = target_time.minute
|
||
|
||
# 判断是否在9:55 - 10:05之间
|
||
is_in_time_range = False
|
||
if target_hour == 9 and target_minute >= 55:
|
||
is_in_time_range = True
|
||
elif target_hour == 10 and target_minute <= 5:
|
||
is_in_time_range = True
|
||
|
||
if not is_in_time_range:
|
||
return
|
||
|
||
today = date.today()
|
||
current_year = today.year
|
||
current_month = today.month
|
||
if current_month == 1:
|
||
last_month = 12
|
||
last_month_year = current_year - 1
|
||
else:
|
||
last_month = current_month - 1
|
||
last_month_year = current_year
|
||
last_month_first_day = date(last_month_year, last_month, 1)
|
||
last_month_last_day = date(current_year, current_month, 1) - timedelta(days=1)
|
||
yesterday = today - timedelta(days=1) # 昨天
|
||
attendance_records = self.env['hr.attendance'].search([
|
||
('current_data', '>=', last_month_first_day),
|
||
('current_data', '<=', yesterday),
|
||
('shift_name', '!=', '休息'),
|
||
])
|
||
employee_abnormal = {}
|
||
for rec in attendance_records:
|
||
emp = rec.employee_id
|
||
emp_id = emp.id
|
||
resource_user_ids = emp.resource_calendar_id.resource_user_ids.ids if emp.resource_calendar_id else []
|
||
if emp_id in resource_user_ids:
|
||
continue
|
||
if emp_id not in employee_abnormal:
|
||
employee_abnormal[emp_id] = {
|
||
'employee': emp,
|
||
'last_month_abnormal': [], # 上月异常列表
|
||
'current_month_abnormal': [] # 本月异常列表
|
||
}
|
||
if rec.is_abnormal:
|
||
att_date = rec.current_data
|
||
date_str = att_date.strftime('%m-%d')
|
||
shift_name = rec.shift_name
|
||
single_abnormal = f"{date_str} {shift_name}"
|
||
if att_date <= last_month_last_day:
|
||
employee_abnormal[emp_id]['last_month_abnormal'].append(single_abnormal)
|
||
else:
|
||
employee_abnormal[emp_id]['current_month_abnormal'].append(single_abnormal)
|
||
|
||
# 4. 统一处理每个员工的提醒(合并上月+本月异常)
|
||
for emp_id, data in employee_abnormal.items():
|
||
emp = data['employee']
|
||
last_month_abnormal = data['last_month_abnormal']
|
||
current_month_abnormal = data['current_month_abnormal']
|
||
vacation_report = self.env['yuthon.vacation.reports'].search([
|
||
('employee_id', '=', emp_id),
|
||
('vacation_date', '=', last_month_first_day),
|
||
], limit=1)
|
||
|
||
# 无需要提醒的异常则跳过:上月异常已确认 且 本月无异常
|
||
if (not last_month_abnormal or (vacation_report and vacation_report.vacation_state == 'confirmed')) and not current_month_abnormal:
|
||
continue
|
||
has_last_month_abnormal = bool(
|
||
last_month_abnormal and (not vacation_report or vacation_report.vacation_state != 'confirmed'))
|
||
has_current_month_abnormal = bool(current_month_abnormal)
|
||
if has_last_month_abnormal and has_current_month_abnormal:
|
||
title = '【上月+本月考勤异常提醒】'
|
||
elif has_last_month_abnormal:
|
||
title = '【上月考勤异常提醒】'
|
||
elif has_current_month_abnormal:
|
||
title = '【本月考勤异常提醒】'
|
||
else:
|
||
continue
|
||
|
||
url = f'https://phone.thtzjt.com/phone/AttendanceSummary/{fields.date.today().year}/{fields.date.today().month}/{emp.user_id.id}'
|
||
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='att',
|
||
user_id=emp.wecom_userid,
|
||
title=f'【{title}】',
|
||
description='考勤异常请尽快处理',
|
||
exception_alerts = True,
|
||
url=url, btntxt="查看详情")
|
||
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='att',
|
||
user_id='18562027762',
|
||
title='【考勤异常提醒】',
|
||
exception_alerts=True,
|
||
description='考勤异常请尽快处理',
|
||
url=url, btntxt="查看详情")
|
||
|
||
def missing_card_judgment(self):
|
||
today = fields.Date.today()
|
||
employee_list = []
|
||
role_ids = self.env['planning.role'].search([
|
||
('name', 'not in', ['厨卫(集团)', '项目运营岗'])
|
||
])
|
||
for role in role_ids:
|
||
for resource_id in role.resource_ids:
|
||
if resource_id.employee_id.id not in employee_list:
|
||
employee_list.append(resource_id.employee_id.id)
|
||
attend_records = self.env['hr.attendance'].search([
|
||
('current_data', '=', today),
|
||
('employee_id', 'not in', employee_list),
|
||
('is_expatriation', '=', False),
|
||
('is_clock_card', '=', False),
|
||
('is_workflow', '=', False),
|
||
('is_approval', '=', False)
|
||
])
|
||
all_employees = self.env['hr.employee'].search([
|
||
('id', 'not in', employee_list)
|
||
])
|
||
|
||
# 判断是否为工作日
|
||
is_workday = self._process_shift_days(today)
|
||
for employee in all_employees:
|
||
# 白名单 和 外派 不做处理
|
||
resource_user_ids = employee.resource_calendar_id.resource_user_ids.ids
|
||
if employee.id not in resource_user_ids:
|
||
# 查找该员工当天的考勤记录
|
||
employee_record = attend_records.filtered(
|
||
lambda r: r.employee_id.id == employee.id
|
||
)
|
||
# 工作日打卡情况判断
|
||
if is_workday:
|
||
if employee_record:
|
||
is_no_card = (not employee_record.check_in or not employee_record.check_out)
|
||
else:
|
||
is_no_card = True
|
||
else:
|
||
is_no_card = False
|
||
|
||
if employee_record:
|
||
# 更新现有记录的缺卡状态
|
||
employee_record.write({'is_no_card': is_no_card})
|
||
if is_no_card == True:
|
||
employee_record.write({'absenteeism_card': True,
|
||
'is_abnormal': True
|
||
})
|
||
|
||
|
||
def _process_shift_days(self, current_data):
|
||
"""
|
||
判断是否是工作日 调休日 双休
|
||
"""
|
||
current_year = (fields.Date.today() + timedelta(hours=8)).year
|
||
query_start = datetime(current_year, 1, 1, 0, 0, 0) - timedelta(hours=8)
|
||
query_end = datetime(current_year, 12, 31, 23, 59, 59) - timedelta(hours=8)
|
||
calendar_leaves = self.env['resource.calendar.leaves'].sudo().search([
|
||
("calendar_id.name", "=", "行政考勤工作日"),
|
||
("date_from", ">=", query_start),
|
||
("date_from", "<=", query_end)
|
||
])
|
||
holidays_list = []
|
||
shift_days_list = []
|
||
# 提取节假日和调休日
|
||
for leave in calendar_leaves:
|
||
if leave.holidays:
|
||
holidays_list.extend(leave.holidays.split(','))
|
||
if leave.alternate_rest_day:
|
||
holidays_list.extend(leave.alternate_rest_day.split(','))
|
||
if leave.shift_replacement_day:
|
||
shift_days_list.extend(leave.shift_replacement_day.split(','))
|
||
|
||
holidays_list = [self.remove_leading_zero(date) for date in holidays_list]
|
||
shift_days_list = [self.remove_leading_zero(date) for date in shift_days_list]
|
||
result = f"{current_data.month}月{current_data.day}日"
|
||
if result in shift_days_list:
|
||
return True
|
||
elif (result in holidays_list) or (current_data.weekday() in [5, 6]):
|
||
return False
|
||
else:
|
||
return True
|
||
|
||
def remove_leading_zero(self, date_str):
|
||
month, day = date_str.split('月')
|
||
month = month.lstrip('0')
|
||
day = day.rstrip('日')
|
||
day = day.lstrip('0')
|
||
return f'{month}月{day}日'
|
||
|
||
@api.depends('employee_id', 'check_in', 'check_out')
|
||
def _compute_display_name(self):
|
||
for attendance in self:
|
||
is_early_card = '早退' if attendance.is_early_card else ''
|
||
is_late_card = '迟到' if attendance.is_late_card else ''
|
||
if not attendance.check_out:
|
||
attendance.display_name = _(
|
||
str(attendance.current_data) + " 上午:%s" + is_late_card + " -下午缺卡",
|
||
format_datetime(self.env, attendance.check_in, dt_format="HH:mrm"),
|
||
)
|
||
elif not attendance.check_in:
|
||
attendance.display_name = _(
|
||
str(attendance.current_data) + " 上午:缺卡-下午%s" + is_early_card,
|
||
format_datetime(self.env, attendance.check_out, dt_format="HH:mm"),
|
||
)
|
||
else:
|
||
attendance.display_name = _(
|
||
str(attendance.current_data) + " 上午:%s" + is_late_card + "-下午:%s" + is_early_card,
|
||
format_datetime(self.env, attendance.check_in, dt_format="HH:mm"),
|
||
format_datetime(self.env, attendance.check_out, dt_format="HH:mm"),
|
||
)
|
||
|
||
def add_comment(self):
|
||
self.ensure_one()
|
||
menu_id = self.env['ir.ui.menu'].search([('name', '=', '我的异常')])
|
||
for rec in self:
|
||
current_date = datetime.today()
|
||
month_start = current_date.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||
month_end = (current_date.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + relativedelta(months=1)) - timedelta(seconds=1)
|
||
domain = [
|
||
('employee_id.user_id', '=', rec.employee_id.user_id.id),
|
||
('check_in', '>=', month_start),
|
||
('check_in', '<=', month_end),
|
||
('week', 'not in', ['seven', 'six'])
|
||
]
|
||
is_no_card = 0
|
||
is_early_card = 0
|
||
is_late_card = 0
|
||
for i in self.search(domain):
|
||
if i.is_no_card:
|
||
is_no_card += 1
|
||
if i.is_no_card:
|
||
is_early_card += 1
|
||
if i.is_no_card:
|
||
is_late_card += 1
|
||
|
||
url = f'https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=hr.attendance&view_type=from&menu_id={menu_id.id}'
|
||
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='att', user_id=rec.employee_id.wecom_userid,
|
||
title="打卡异常提醒", description="本月迟到: {}\n本月缺卡: {}\n本月早退: {}\n".format(is_late_card, is_no_card, is_early_card),
|
||
url=url, btntxt="详细信息")
|
||
|
||
def update_state(self):
|
||
attendance_ids = self.env["hr.attendance"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id'))
|
||
)
|
||
for rec in attendance_ids:
|
||
rec.write({'is_no_card': False,
|
||
'is_early_card': False,
|
||
'shift_name': False,
|
||
'is_holiday': False,
|
||
'is_week': False,
|
||
'is_late_card': False,
|
||
'is_abnormal': False,
|
||
'is_warning': False,
|
||
'absenteeism_card': False
|
||
})
|
||
|
||
def button_controls_wizard(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "操作弹窗",
|
||
"res_model": "attendance.controls.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
"context": {
|
||
"default_attendance_id": self.id
|
||
}
|
||
}
|
||
|
||
def revoke(self):
|
||
self.check_in = self.approval_check_in
|
||
self.correct_result = None
|
||
self.check_out = self.approval_check_out
|
||
self.is_no_card = self.is_approval_no
|
||
self.is_early_card = self.is_approval_early
|
||
self.is_late_card = self.is_approval_late
|
||
self.is_abnormal = self.is_approval_abnormal
|
||
self.absenteeism_card = self.is_approval_abs
|
||
self.is_approval = False
|
||
if self.is_check_in_red == True:
|
||
self.is_check_in_red = False
|
||
if self.is_check_out_red == True:
|
||
self.is_check_out_red = False
|
||
|
||
def button_correct_wizard(self):
|
||
self.approval_check_in = self.check_in
|
||
self.approval_check_out = self.check_out
|
||
self.is_approval_no = self.is_no_card
|
||
self.is_approval_early = self.is_early_card
|
||
self.is_approval_late = self.is_late_card
|
||
self.is_approval_abnormal = self.is_abnormal
|
||
self.is_approval_abs = self.absenteeism_card
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "修正弹窗",
|
||
"res_model": "attendance.correct.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
"context": {
|
||
"default_attendance_ids": [(6, 0, [self.id])]
|
||
}
|
||
}
|
||
|
||
def all_button_correct_wizard(self):
|
||
self.approval_check_in = self.check_in
|
||
self.approval_check_out = self.check_out
|
||
self.is_approval_no = self.is_no_card
|
||
self.is_approval_early = self.is_early_card
|
||
self.is_approval_late = self.is_late_card
|
||
self.is_approval_abnormal = self.is_abnormal
|
||
self.is_approval_abs = self.absenteeism_card
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "修正弹窗",
|
||
"res_model": "attendance.correct.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
"context": {
|
||
"default_attendance_ids": [(6, 0, [self.id])]
|
||
}
|
||
}
|
||
|
||
def button_compensatory_leave(self):
|
||
attendance_ids = self.env["hr.attendance"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id'))
|
||
)
|
||
for record in attendance_ids:
|
||
record.approval_check_in = record.check_in
|
||
record.approval_check_out = record.check_out
|
||
record.is_approval_no = record.is_no_card
|
||
record.is_approval_early = record.is_early_card
|
||
record.is_approval_late = record.is_late_card
|
||
record.is_approval_abnormal = record.is_abnormal
|
||
record.is_approval_abs = record.absenteeism_card
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "修正弹窗",
|
||
"res_model": "attendance.correct.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
"context": {
|
||
"default_attendance_ids": [(6, 0, attendance_ids.ids)]
|
||
}
|
||
}
|
||
|
||
def ensure_september_records(self):
|
||
"""
|
||
确保选择期间每位员工至少有一条记录
|
||
若没有则创建一条默认记录
|
||
"""
|
||
start_date = fields.Date.from_string('2026-01-01')
|
||
end_date = fields.Date.from_string('2026-01-31')
|
||
|
||
existing_employees = self.env['hr.employee'].search([('name', 'not in', ['Administrator'])])
|
||
if not existing_employees:
|
||
return
|
||
|
||
# 计算日期范围内的所有日期
|
||
current_date = start_date
|
||
date_list = []
|
||
while current_date <= end_date:
|
||
date_list.append(current_date)
|
||
current_date += timedelta(days=1)
|
||
|
||
existing_records = self.search([
|
||
('employee_id', 'in', existing_employees.ids),
|
||
('current_data', '>=', fields.Date.to_string(start_date)),
|
||
('current_data', '<=', fields.Date.to_string(end_date + timedelta(days=1)))
|
||
])
|
||
existing_map = {}
|
||
for record in existing_records:
|
||
if not record.current_data:
|
||
continue
|
||
key = (record.employee_id.id, fields.Date.to_string(record.current_data))
|
||
existing_map[key] = True
|
||
|
||
# 检查每个员工在每个日期是否有记录
|
||
for employee in existing_employees:
|
||
for date in date_list:
|
||
date_str = fields.Date.to_string(date)
|
||
key = (employee.id, date_str)
|
||
|
||
# 如果没有记录,则添加到创建列表
|
||
if key not in existing_map:
|
||
new_record = self.create({
|
||
'employee_id': employee.id,
|
||
'current_data': date_str,
|
||
'check_in': False,
|
||
'check_out': False,
|
||
'is_no_card': False,
|
||
'is_abnormal': False
|
||
})
|
||
new_record.compute_attend()
|
||
|
||
|
||
def button_source(self):
|
||
employee_id = self.employee_id
|
||
def get_all_upper_depts(dept):
|
||
"""递归获取部门及其所有上级部门的ID列表"""
|
||
dept_ids = [dept.id]
|
||
if dept.parent_id:
|
||
dept_ids.extend(get_all_upper_depts(dept.parent_id))
|
||
return dept_ids
|
||
all_dept_ids = get_all_upper_depts(employee_id.department_id)
|
||
current_date = self.current_data
|
||
slot_start = datetime.combine(current_date, datetime.min.time())
|
||
slot_end = datetime.combine(current_date + timedelta(days=1), datetime.min.time())
|
||
# 查找员工对应的班次
|
||
planning_slots = self.env['planning.slot'].search([
|
||
('employee_id', '=', employee_id.id),
|
||
('start_datetime', '>=', slot_start),
|
||
('start_datetime', '<', slot_end),
|
||
], limit=1)
|
||
planning_name = planning_slots.planning_name if planning_slots else None
|
||
template_domain = []
|
||
if planning_name:
|
||
template_domain.append(('planning_name', '=', planning_name))
|
||
template_domain.append(('department_id', 'in', all_dept_ids))
|
||
template = self.env['planning.slot.template'].search(template_domain, limit=1)
|
||
if not template:
|
||
template = self.env['planning.slot.template'].search([('role_id.name', '=', '集团行政')], limit=1)
|
||
role_templates = [{'role_name': '厨卫(集团)', 'template': None}, {'role_name': '项目运营岗', 'template': None}]
|
||
for item in role_templates:
|
||
item['template'] = self.env['planning.slot.template'].search([('role_id.name', '=', item['role_name'])], )
|
||
for item in role_templates:
|
||
if item['template']:
|
||
employee_ids = item['template'].role_id.resource_ids.employee_id.ids or []
|
||
employee_list = list(employee_ids)
|
||
if employee_id.id in employee_list:
|
||
template = item['template']
|
||
break
|
||
next_day = current_date + timedelta(days=1)
|
||
start_datetime = datetime.combine(current_date, datetime.min.time()) - timedelta(hours=8)
|
||
check_out_end = self.float_to_datetime(current_date, template.check_out_end)
|
||
if template.check_out_start == 0.0:
|
||
template.check_out_start = 24.0
|
||
if template.check_out_end < template.check_out_start:
|
||
check_out_end = self.float_to_datetime(next_day, template.check_out_end)
|
||
domain = [
|
||
('employee_id', '=', employee_id.id),
|
||
('checkin_time', '>=', start_datetime),
|
||
('checkin_time', '<=', check_out_end - timedelta(hours=8))
|
||
]
|
||
if '行政' in template.display_name:
|
||
domain = [
|
||
('employee_id', '=', employee_id.id),
|
||
('checkin_time', '>=', current_date),
|
||
('checkin_time', '<', next_day)
|
||
]
|
||
attendance_records = self.env['hr.attendance.detail'].search(domain)
|
||
if attendance_records:
|
||
action = {
|
||
'name': '考勤记录列表',
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': 'hr.attendance.detail',
|
||
'view_mode': 'tree,form',
|
||
'target': 'new',
|
||
'domain': domain,
|
||
}
|
||
return action
|
||
else:
|
||
_logger.info(f"当前过滤条件为开始日期-----{start_datetime}结束日期------{check_out_end - timedelta(hours=8)}")
|
||
raise ValidationError("该员工当天没有考勤记录!")
|
||
|
||
def float_to_datetime(self, base_date, hour_float):
|
||
hours = int(hour_float)
|
||
minutes = int((hour_float - hours) * 60)
|
||
return datetime.combine(base_date, datetime.min.time()) + timedelta(hours=hours, minutes=minutes)
|
||
|
||
def button_card_approval(self):
|
||
form_id = self.env.ref('yuthon_hr_attendance.card_approval_form').id
|
||
if self.is_approval:
|
||
raise ValidationError("该补卡已提交申请!")
|
||
else:
|
||
self.is_approval = True
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "补卡申请",
|
||
"res_model": "card.approval",
|
||
"views": [[form_id, "form"]],
|
||
"context": {
|
||
'default_attendance_id': self.id,
|
||
'default_users_id': self.employee_id.user_id.id,
|
||
'default_shift_id':self.shift_id.id
|
||
}
|
||
}
|
||
|
||
def button_duty_approval(self):
|
||
form_id = self.env.ref('yuthon_hr_attendance.go_approval_form').id
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "外出申请",
|
||
"res_model": "go.approval",
|
||
"views": [[form_id, "form"]],
|
||
"context": {
|
||
'default_is_day': True,
|
||
'default_start_date': self.current_data,
|
||
'default_end_date': self.current_data,
|
||
'default_users_id': self.employee_id.user_id.id,
|
||
}
|
||
}
|
||
|
||
|
||
def button_batch_correct(self):
|
||
attendance_ids = self.env["hr.attendance"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id'))
|
||
)
|
||
attendance_ids.fixes()
|
||
|
||
def fixes(self):
|
||
for record in self:
|
||
record.approval_check_in = record.check_in
|
||
record.approval_check_out = record.check_out
|
||
record.is_approval_no = record.is_no_card
|
||
record.is_approval_early = record.is_early_card
|
||
record.is_approval_late = record.is_late_card
|
||
record.is_approval_abnormal = record.is_abnormal
|
||
record.is_approval_abs = record.absenteeism_card
|
||
values = {
|
||
'is_approval': True,
|
||
'is_abnormal': False,
|
||
'is_no_card': False,
|
||
'is_early_card': False,
|
||
'is_late_card': False,
|
||
'is_check_in_red': False,
|
||
'is_check_out_red': False,
|
||
'absenteeism_card': False,
|
||
}
|
||
if not record.shift_id:
|
||
year = record.current_data.year
|
||
month = record.current_data.month
|
||
day = record.current_data.day
|
||
current_date = datetime.today()
|
||
|
||
calendar_attendances = record.employee_id.resource_id.calendar_id.attendance_ids
|
||
if not calendar_attendances:
|
||
continue
|
||
|
||
min_hour_from = calendar_attendances[0].hour_from
|
||
min_hour = int(min_hour_from)
|
||
min_minute = int((min_hour_from - min_hour) * 60)
|
||
will_datetime_start = current_date.replace(
|
||
year=year, month=month, day=day,
|
||
hour=min_hour, minute=min_minute,
|
||
second=0, microsecond=0
|
||
) - timedelta(hours=8)
|
||
|
||
max_hour_to = calendar_attendances[-1].hour_to
|
||
max_hour = int(max_hour_to)
|
||
max_minute = int((max_hour_to - max_hour) * 60)
|
||
will_datetime_end = current_date.replace(
|
||
year=year, month=month, day=day,
|
||
hour=max_hour, minute=max_minute,
|
||
second=0, microsecond=0
|
||
) - timedelta(hours=8)
|
||
|
||
if not record.check_in:
|
||
values['check_in'] = will_datetime_start
|
||
values['is_check_in_red'] = True
|
||
values['is_no_card'] = False
|
||
elif record.check_in > will_datetime_start:
|
||
values['check_in'] = will_datetime_start
|
||
values['is_check_in_red'] = True
|
||
values['is_late_card'] = False
|
||
|
||
if not record.check_out:
|
||
values['check_out'] = will_datetime_end
|
||
values['is_check_out_red'] = True
|
||
values['is_no_card'] = False
|
||
elif record.check_out < will_datetime_end:
|
||
values['check_out'] = will_datetime_end
|
||
values['is_check_out_red'] = True
|
||
values['is_early_card'] = False
|
||
else:
|
||
shift = record.shift_id
|
||
shift_template = shift.template_id
|
||
start_time = shift_template.start_time
|
||
end_time = shift_template.end_time
|
||
end_time2 = shift_template.end_time2
|
||
|
||
start_hour = int(start_time)
|
||
start_minute = int((start_time - start_hour) * 60)
|
||
end_hour = int(end_time)
|
||
end_minute = int((end_time - end_hour) * 60)
|
||
end2_hour = int(end_time2)
|
||
end2_minute = int((end_time2 - end2_hour) * 60)
|
||
|
||
shift_date = shift.start_datetime.date()
|
||
normal_check_in = datetime.combine(shift_date, datetime.min.time()).replace(
|
||
hour=start_hour, minute=start_minute, second=0, microsecond=0
|
||
) - timedelta(hours=8)
|
||
|
||
normal_check_out = datetime.combine(shift_date, datetime.min.time()).replace(
|
||
hour=end_hour, minute=end_minute, second=0, microsecond=0
|
||
) - timedelta(hours=8)
|
||
|
||
if shift_template.day2 == '次日' and shift_template.planning_name != '中夜':
|
||
normal_check_out = datetime.combine(shift_date, datetime.min.time()).replace(
|
||
hour=end2_hour, minute=end2_minute, second=0, microsecond=0
|
||
) - timedelta(hours=8) + timedelta(days=1)
|
||
|
||
if not record.check_in:
|
||
values['check_in'] = normal_check_in
|
||
values['is_check_in_red'] = True
|
||
values['is_no_card'] = False
|
||
elif record.check_in > normal_check_in:
|
||
values['check_in'] = normal_check_in
|
||
values['is_check_in_red'] = True
|
||
values['is_late_card'] = False
|
||
|
||
if not record.check_out:
|
||
values['check_out'] = normal_check_out
|
||
values['is_check_out_red'] = True
|
||
values['is_no_card'] = False
|
||
elif record.check_out < normal_check_out:
|
||
values['check_out'] = normal_check_out
|
||
values['is_check_out_red'] = True
|
||
values['is_early_card'] = False
|
||
record.write(values)
|
||
|
||
@api.depends('current_data')
|
||
def _compute_week(self):
|
||
for record in self:
|
||
if record.current_data:
|
||
weekday = (record.current_data + timedelta(hours=8)).weekday()
|
||
weekdays_cn = {0: 'one', 1: 'two', 2: 'three',
|
||
3: 'four', 4: 'five', 5: 'six',
|
||
6: 'seven'}
|
||
record.week = weekdays_cn[weekday]
|
||
else:
|
||
record.week = False
|
||
|
||
def _common_card_remind(self, dept_name=None, planning_name=None, is_check_in=True, method_name=None, is_executive=False):
|
||
"""通用打卡提醒函数"""
|
||
if not method_name:
|
||
return
|
||
current_date = fields.Date.today() + timedelta(hours=8)
|
||
cron_code = f'model.{method_name}()'
|
||
cron = self.env['ir.cron'].search([('code', 'like', cron_code)], limit=1)
|
||
next_call = cron.nextcall + timedelta(hours=8)
|
||
start_window = next_call - timedelta(minutes=5)
|
||
end_window = next_call + timedelta(minutes=5)
|
||
if not (start_window <= fields.Datetime.now() + timedelta(hours=8) <= end_window):
|
||
return
|
||
if is_executive:
|
||
domain = [('current_data', '=', current_date)]
|
||
attendances = self.env['hr.attendance'].search(domain)
|
||
if self._process_shift_days(current_date): # 如果是工作日则提醒
|
||
attended_employee_ids = attendances.mapped('employee_id')
|
||
for employee in attended_employee_ids:
|
||
if is_check_in:
|
||
is_card = self.env['hr.attendance'].search([('employee_id', '=', employee.id), ('check_in', '!=', False), ('current_data', '=', current_date)], limit=1)
|
||
if not is_card:
|
||
self.card_in_comment(employee, '行政班次')
|
||
else:
|
||
is_card = self.env['hr.attendance'].search([('employee_id', '=', employee.id), ('check_out', '!=', False), ('current_data', '=', current_date)], limit=1)
|
||
if not is_card:
|
||
self.card_out_comment(employee, '行政班次')
|
||
else:
|
||
slot_start = datetime.combine(current_date, datetime.min.time())
|
||
slot_end = datetime.combine(current_date + timedelta(days=1), datetime.min.time())
|
||
domain = [
|
||
('template_id.planning_name', '=', planning_name),
|
||
('start_datetime', '>=', slot_start),
|
||
('start_datetime', '<', slot_end),
|
||
('role_id.name', '=', dept_name),
|
||
]
|
||
planning_slots = self.env['planning.slot'].search(domain)
|
||
for slot in planning_slots:
|
||
if is_check_in:
|
||
is_card = self.env['hr.attendance'].search([('employee_id', '=', slot.employee_id.id), ('check_in', '!=', False), ('current_data', '=', current_date)], limit=1)
|
||
if not is_card:
|
||
self.card_in_comment(slot.employee_id, planning_name)
|
||
else:
|
||
is_card = self.env['hr.attendance'].search([('employee_id', '=', slot.employee_id.id), ('check_out', '!=', False), ('current_data', '=', current_date)], limit=1)
|
||
if not is_card:
|
||
self.card_out_comment(slot.employee_id, planning_name)
|
||
|
||
# 保安部 - 早班
|
||
def ba_card_white_up(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='早班', is_check_in=True,
|
||
method_name='ba_card_white_up')
|
||
|
||
def ba_card_white_down(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='早班', is_check_in=False,
|
||
method_name='ba_card_white_down')
|
||
|
||
# 保安部 - 中班
|
||
def ba_card_mid_up(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='中班', is_check_in=True,
|
||
method_name='ba_card_mid_up')
|
||
|
||
def ba_card_mid_down(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='中班', is_check_in=False,
|
||
method_name='ba_card_mid_down')
|
||
|
||
# 保安部 - 夜班
|
||
def ba_card_night_up(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='夜班', is_check_in=True,
|
||
method_name='ba_card_night_up')
|
||
|
||
def ba_card_night_down(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='夜班', is_check_in=False,
|
||
method_name='ba_card_night_down')
|
||
|
||
# 保安部 - 旧晚(旧区府晚班)
|
||
def ba_card_old_night_up(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='旧晚(旧区府晚班)', is_check_in=True,
|
||
method_name='ba_card_old_night_up')
|
||
|
||
def ba_card_old_night_down(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='旧晚(旧区府晚班)', is_check_in=False,
|
||
method_name='ba_card_old_night_down')
|
||
|
||
# 保安部 - 旧白(旧区府白班)
|
||
def ba_card_old_white_up(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='旧白(旧区府白班)', is_check_in=True,
|
||
method_name='ba_card_old_white_up')
|
||
|
||
def ba_card_old_white_down(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='旧白(旧区府白班)', is_check_in=False,
|
||
method_name='ba_card_old_white_down')
|
||
|
||
# 工程部 - 白班
|
||
def gc_white_in_remind(self):
|
||
self._common_card_remind(dept_name='工程部', planning_name='白班', is_check_in=True,
|
||
method_name='gc_white_in_remind')
|
||
|
||
def gc_white_out_remind(self):
|
||
self._common_card_remind(dept_name='工程部', planning_name='白班', is_check_in=False,
|
||
method_name='gc_white_out_remind')
|
||
|
||
# 工程部 - 白值
|
||
def gc_white_remind_in(self):
|
||
self._common_card_remind(dept_name='工程部', planning_name='白值', is_check_in=True,
|
||
method_name='gc_white_remind_in')
|
||
|
||
def gc_white_remind_out(self):
|
||
self._common_card_remind(dept_name='工程部', planning_name='白值', is_check_in=False,
|
||
method_name='gc_white_remind_out')
|
||
|
||
# 工程部 - 中夜
|
||
def gc_night_remind_in(self):
|
||
self._common_card_remind(dept_name='工程部', planning_name='中夜', is_check_in=True,
|
||
method_name='gc_night_remind_in')
|
||
|
||
def gc_night_remind_out(self):
|
||
self._common_card_remind(dept_name='工程部', planning_name='中夜', is_check_in=False,
|
||
method_name='gc_night_remind_out')
|
||
|
||
# 工程部 - 旧晚(旧区府晚班)
|
||
def card_old_night_remind_in(self):
|
||
self._common_card_remind(dept_name='工程部', planning_name='旧晚(旧区府晚班)', is_check_in=True,
|
||
method_name='card_old_night_remind_in')
|
||
|
||
def card_old_night_remind_out(self):
|
||
self._common_card_remind(dept_name='工程部', planning_name='旧晚(旧区府晚班)', is_check_in=False,
|
||
method_name='card_old_night_remind_out')
|
||
|
||
# 工程部 - 旧白(旧区府白班)
|
||
def card_old_day_remind_in(self):
|
||
self._common_card_remind(dept_name='工程部', planning_name='旧白(旧区府白班)', is_check_in=True,
|
||
method_name='card_old_day_remind_in')
|
||
|
||
def card_old_day_remind_out(self):
|
||
self._common_card_remind(dept_name='工程部', planning_name='旧白(旧区府白班)', is_check_in=False,
|
||
method_name='card_old_day_remind_out')
|
||
|
||
# 粮储-白班
|
||
def card_lc_remind_in(self):
|
||
self._common_card_remind(dept_name='联安粮库', planning_name='白班', is_check_in=True,
|
||
method_name='card_lc_remind_in')
|
||
|
||
def card_lc_remind_out(self):
|
||
self._common_card_remind(dept_name='联安粮库', planning_name='白班', is_check_in=False,
|
||
method_name='card_lc_remind_out')
|
||
|
||
# 粮储-值班(白值)
|
||
def card_lc_shift_remind_in(self):
|
||
self._common_card_remind(dept_name='联安粮库', planning_name='白值', is_check_in=True,
|
||
method_name='card_lc_shift_remind_in')
|
||
|
||
def card_lc_shift_remind_out(self):
|
||
self._common_card_remind(dept_name='联安粮库', planning_name='白值', is_check_in=False,
|
||
method_name='card_lc_shift_remind_out')
|
||
|
||
# 保安计划行政
|
||
def card_lc_shift_remind_on_administrative(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='保安计划行政', is_check_in=True,
|
||
method_name='card_lc_shift_remind_on_administrative')
|
||
|
||
def card_lc_shift_remind_out_administrative(self):
|
||
self._common_card_remind(dept_name='保安部', planning_name='保安计划行政', is_check_in=False,
|
||
method_name='card_lc_shift_remind_out_administrative')
|
||
|
||
# 行政班次
|
||
def executive_day_remind_in(self):
|
||
self._common_card_remind(planning_name='行政班次', is_check_in=True,
|
||
method_name='executive_day_remind_in', is_executive=True)
|
||
|
||
def executive_day_remind_out(self):
|
||
self._common_card_remind(planning_name='行政班次', is_check_in=False,
|
||
method_name='executive_day_remind_out', is_executive=True)
|
||
|
||
def card_in_comment(self, employee_id, planning_name):
|
||
url = '#小程序://天投云/XttsPpKg3MEpY7h'
|
||
content_item = [{"key": "班次", "value": planning_name}, {"key": "类型", "value": "上班"}]
|
||
try:
|
||
self.sudo().env["wecom.apps"].sync_send_message_textcard(
|
||
category='att', user_id=employee_id.wecom_userid,
|
||
description="记得打卡", title="打卡提醒",
|
||
url=url, btntxt="详细信息", content_item=content_item,)
|
||
except Exception as e:
|
||
_logger.error(f"上班打卡提醒发送失败:员工={employee_id.name}, 错误={e}")
|
||
|
||
def card_out_comment(self, employee_id, planning_name):
|
||
url = '#小程序://天投云/XttsPpKg3MEpY7h'
|
||
content_item = [{"key": "班次", "value": planning_name}, {"key": "类型", "value": "下班"}]
|
||
try:
|
||
self.sudo().env["wecom.apps"].sync_send_message_textcard(
|
||
category='att', user_id=employee_id.wecom_userid,
|
||
description="记得打卡", title="打卡提醒",
|
||
url=url, btntxt="详细信息", content_item=content_item, )
|
||
except Exception as e:
|
||
_logger.error(f"下班打卡提醒发送失败:员工={employee_id.name}, 错误={e}")
|
||
|
||
def test_card(self, user_id):
|
||
content_item = [{"key": "班次", "value": "测试行政"}, {"key": "类型", "value": "下班"}]
|
||
self.sudo().env["wecom.apps"].sync_send_message_textcard(
|
||
category='att', user_id=str(user_id),
|
||
description="记得打卡", title="打卡提醒",
|
||
content_item=content_item, )
|
||
|
||
|
||
def reminder_card(self):
|
||
current_date = (fields.Date.today() + relativedelta(hours=8)).date()
|
||
now = fields.datetime.now() + timedelta(hours=8)
|
||
reminder_ids = self.env['message.reminder.tasks'].search(
|
||
[('state', '=', 'todo'), ('current_data', '=', current_date)])
|
||
for task in reminder_ids:
|
||
hour1 = (task.reminder_time + timedelta(hours=8)).hour
|
||
minute1 = (task.reminder_time + timedelta(hours=8)).minute
|
||
if minute1 == now.minute and hour1 == now.hour:
|
||
employee_id = self.env['hr.employee'].search([('name', '=', task.name)])
|
||
if task.type == 'work':
|
||
self.card_in_comment(employee_id, task.shift_name)
|
||
else:
|
||
self.card_out_comment(employee_id, task.shift_name)
|
||
|
||
def update_abnormal(self):
|
||
attendance_ids = self.env["hr.attendance"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
for attendance in attendance_ids:
|
||
attendance.set_shift_name()
|
||
|
||
def set_shift_name(self):
|
||
"""抽取的统一班次名称设置逻辑"""
|
||
for record in self:
|
||
# 白名单不做处理
|
||
if record.shift_id or record.employee_id.id in record.employee_id.resource_calendar_id.resource_user_ids.ids:
|
||
continue # 已有班次记录,无需自动设置
|
||
|
||
shift_days_list = []
|
||
is_shift_replacement = False
|
||
is_holiday = False
|
||
is_weekend = False
|
||
# 判断排班员工
|
||
planning_ids = self.env['planning.role'].search([('name', 'in', ['保安部', '工程部', '联安粮库'])])
|
||
related_resources = set()
|
||
for role in planning_ids:
|
||
if role.resource_ids:
|
||
related_resources.update(role.resource_ids.ids)
|
||
related_resources_set = self.env['resource.resource'].browse(related_resources)
|
||
if record.employee_id.resource_id in related_resources_set:
|
||
record.shift_name = '休息'
|
||
record.is_week = True
|
||
record.write({
|
||
'is_early_card': False,
|
||
'is_late_card': False,
|
||
'is_abnormal': False,
|
||
'is_warning': False,
|
||
'is_no_card': False,
|
||
'absenteeism_card': False,
|
||
})
|
||
continue
|
||
|
||
if record.current_data:
|
||
current_year = record.current_data.year
|
||
query_start = datetime(current_year, 1, 1, 0, 0, 0) - timedelta(hours=8)
|
||
query_end = datetime(current_year, 12, 31, 23, 59, 59) - timedelta(hours=8)
|
||
leave_records = self.env['resource.calendar.leaves'].search([
|
||
("calendar_id.name", "=", "行政考勤工作日"),
|
||
("date_from", ">=", query_start),
|
||
("date_from", "<=", query_end)
|
||
])
|
||
# 检查是否为节假日
|
||
for leave in leave_records:
|
||
if leave.date_from and leave.date_to:
|
||
from_date = fields.Date.to_date(leave.date_from)
|
||
to_date = fields.Date.to_date(leave.date_to)
|
||
if from_date <= record.current_data <= to_date:
|
||
is_holiday = True # 标记为节假日
|
||
|
||
# 收集调班补班日期
|
||
if leave.shift_replacement_day:
|
||
shift_days_list.extend(leave.shift_replacement_day.split(','))
|
||
# 检查是否为调班补班日
|
||
target_day_str = f"{record.current_data.month}月{record.current_data.day}日"
|
||
if target_day_str in shift_days_list:
|
||
is_shift_replacement = True
|
||
|
||
# 检查是否为周末
|
||
weekday = record.current_data.isoweekday()
|
||
if weekday in [6, 7]:
|
||
is_weekend = True
|
||
|
||
if is_shift_replacement:
|
||
record.shift_name = '行政班次'
|
||
elif is_weekend or is_holiday:
|
||
record.shift_name = '休息'
|
||
else:
|
||
record.shift_name = '行政班次'
|
||
if record.is_expatriation:
|
||
record.shift_name = '外派'
|
||
|
||
if record.shift_name == "休息" and not is_holiday:
|
||
record.is_week = is_weekend
|
||
else:
|
||
record.is_week = False
|
||
|
||
record.is_holiday = is_holiday
|
||
|
||
@api.model
|
||
def create(self, vals):
|
||
record = super(HrAttendance, self).create(vals)
|
||
# 未来日期不处理班次(保持原逻辑)
|
||
if 'current_data' in vals:
|
||
current_data = fields.Date.to_date(vals['current_data'])
|
||
today = fields.Date.today()
|
||
if current_data > today:
|
||
return record
|
||
record.set_shift_name()
|
||
return record
|
||
|
||
def write(self, vals):
|
||
result = super(HrAttendance, self).write(vals)
|
||
# 当更新考勤日期或班次时,重新计算班次名称
|
||
if 'current_data' in vals or 'shift_id' in vals:
|
||
self.set_shift_name()
|
||
for record in self:
|
||
self._update_vacation_report(record)
|
||
return result
|
||
|
||
def unlink(self):
|
||
for record in self:
|
||
self._update_vacation_report(record)
|
||
result = super(HrAttendance, self).unlink()
|
||
return result
|
||
|
||
|
||
def _update_vacation_report(self, record):
|
||
"""更新相关的假期报表日志"""
|
||
if record.current_data and record.employee_id:
|
||
current_date = record.current_data.replace(day=1)
|
||
report = self.env['yuthon.vacation.reports'].search([
|
||
('employee_id', '=', record.employee_id.id),
|
||
('vacation_date', '=', current_date)
|
||
], limit=1)
|
||
if report:
|
||
report.compute_attendance_fields()
|
||
|
||
def button_all(self):
|
||
attendance_ids = self.env["hr.attendance"].browse(self._context.get('active_ids', self._context.get('active_id')))
|
||
for record in attendance_ids:
|
||
if not record.shift_id:
|
||
shift_days_list = []
|
||
is_shift_replacement = False
|
||
is_leave_day = False
|
||
is_weekend = False
|
||
if record.current_data:
|
||
# 查询休假记录
|
||
current_year = (fields.Date.today() + timedelta(hours=8)).year
|
||
query_start = datetime(current_year, 1, 1, 0, 0, 0) - timedelta(hours=8)
|
||
query_end = datetime(current_year, 12, 31, 23, 59, 59) - timedelta(hours=8)
|
||
leave_records = self.env['resource.calendar.leaves'].search(
|
||
[("calendar_id.name", "=", "行政考勤工作日"),
|
||
("date_from", ">=", query_start),
|
||
("date_from", "<=", query_end)])
|
||
# 检查是否存在匹配的休假日期
|
||
for leave in leave_records:
|
||
if leave.date_from and leave.date_to:
|
||
# 提取休假记录的月日范围
|
||
from_date = fields.Date.to_date(leave.date_from) + timedelta(days=1)
|
||
to_date = fields.Date.to_date(leave.date_to) + timedelta(days=1)
|
||
if from_date <= record.current_data <= to_date:
|
||
is_leave_day = True
|
||
if leave.shift_replacement_day:
|
||
shift_days_list.extend(leave.shift_replacement_day.split(',')) # 调班补班
|
||
|
||
target_day_str = f"{record.current_data.month}月{record.current_data.day}日"
|
||
if target_day_str in shift_days_list:
|
||
is_shift_replacement = True
|
||
|
||
weekday = record.current_data.isoweekday()
|
||
if weekday in [6, 7]:
|
||
is_weekend = True
|
||
|
||
if is_leave_day:
|
||
record.shift_name = ''
|
||
elif is_shift_replacement:
|
||
record.shift_name = '行政班次'
|
||
elif is_weekend:
|
||
record.shift_name = ''
|
||
else:
|
||
record.shift_name = '行政班次'
|
||
|
||
# 定时任务主方法
|
||
def action_update_attendance(self):
|
||
pass #todo 待过
|
||
|
||
|
||
def update_attendance(self, employee, current_date):
|
||
current_date_str = fields.Date.to_string(current_date)
|
||
leave_domain = [
|
||
('employee_id', '=', employee.id),
|
||
('date_from', '<=', fields.Datetime.to_string(datetime.combine(current_date, datetime.max.time()))),
|
||
('date_to', '>=', fields.Datetime.to_string(datetime.combine(current_date, datetime.min.time()))),
|
||
('state', '=', 'validate')
|
||
]
|
||
leave_records = self.env['hr.leave'].search(leave_domain)
|
||
if leave_records:
|
||
for leave in leave_records:
|
||
if leave.batch_id:
|
||
main_leave_record = leave.batch_id
|
||
elif leave.hour_batch_id:
|
||
main_leave_record = leave.hour_batch_id
|
||
workflow = self.env['soong.workflow.record'].search([
|
||
('code', '=', main_leave_record.code)
|
||
], limit=1)
|
||
self.write({
|
||
'workflow_id': workflow.id,
|
||
'is_workflow': True,
|
||
'is_no_card': False,
|
||
'is_early_card': False,
|
||
'is_late_card': False,
|
||
'is_abnormal': False,
|
||
'absenteeism_card': False
|
||
})
|
||
else:
|
||
self.write({
|
||
'workflow_id': False,
|
||
'is_workflow': False,
|
||
})
|
||
|
||
go_approval_domain = [
|
||
('employee_ids', 'in', employee.id),
|
||
('start_date', '<=', current_date_str),
|
||
('end_date', '>=', current_date_str),
|
||
('work_end', '=', True),
|
||
]
|
||
go_approval_record = self.env['go.approval'].search(go_approval_domain, limit=1)
|
||
if go_approval_record:
|
||
workflow = self.env['soong.workflow.record'].search([
|
||
('code', '=', go_approval_record.code)
|
||
], limit=1)
|
||
if workflow and workflow.approve_state == 'end':
|
||
self.write({
|
||
'workflow_id': workflow.id,
|
||
'is_workflow': True,
|
||
'is_no_card': False,
|
||
'is_early_card': False,
|
||
'is_late_card': False,
|
||
'is_abnormal': False,
|
||
'absenteeism_card': False
|
||
})
|
||
|
||
|
||
class card_remind_form(models.Model):
|
||
_name = 'card.remind.form'
|
||
_description = '提醒日志'
|
||
|
||
name = fields.Char(string='姓名')
|
||
start_time = fields.Datetime(string='时间')
|
||
shift = fields.Char(string='班次')
|
||
department = fields.Char(string='部门')
|
||
remarks = fields.Text(string='备注')
|
||
|
||
class MedicalRulesSetting(models.Model):
|
||
_name = 'medical.rules.setting' # 数据库keyerror错误
|