添加功能

This commit is contained in:
zhou
2025-07-28 10:50:22 +08:00
parent 3a2aa43176
commit d6fe8bf577
3 changed files with 142 additions and 131 deletions
@@ -4,6 +4,8 @@ import calendar
from odoo import api, fields, models, tools, _
from odoo.tools.float_utils import float_round
from dateutil.relativedelta import relativedelta
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from datetime import timedelta
from datetime import date
@@ -84,7 +86,7 @@ class HrAttendanceMonthDetail(models.Model):
department_id = fields.Many2one('hr.department', string="部门", related="employee_id.department_id", readonly=True, store=True)
# userid = fields.Char(string='userid')
attendance_year_id = fields.Many2one('hr.attendance.year', string="统计年份")
current_year = fields.Char(string='统计年份', related="attendance_year_id.year")
current_year = fields.Char(string='统计年份', related="attendance_year_id.year", store=True)
current_month = fields.Selection([
('1', '1月份'),
('2', '2月份'),
@@ -98,7 +100,7 @@ class HrAttendanceMonthDetail(models.Model):
('10', '10月份'),
('11', '11月份'),
('12', '12月份')
], string="考勤月份")
], string="考勤月份", store=True)
required_attendance_days = fields.Integer(string="应出勤天数")
actual_attendance_days = fields.Integer(string="实际出勤天数")
compensatory_leave = fields.Integer(string="调休")
@@ -318,24 +320,18 @@ class HrAttendanceMonthDetail(models.Model):
is_no_card += 1
return is_late_card, is_early_card, is_no_card
def _hr_attendance_month_detail_action(self):
def _hr_attendance_month_detail_action(self, processed_employee_ids):
"""
返回视图 view_hr_attendance_month_detail_tree
:return: ir.actions.act_window 视图 view_hr_attendance_month_detail_tree
"""
self.ensure_one()
action = {
'type': 'ir.actions.act_window',
'res_model': 'hr.attendance.month.detail',
'name': "考勤统计",
'view_type': 'tree',
'view_mode': 'list,form',
'context': {
"current_year": self.current_year,
"current_month": self.current_month,
},
'domain': [('current_year', '=', self.current_year), ('current_month', '=', self.current_month)],
'target': 'main',
'domain': [('employee_id', 'in', processed_employee_ids)] if processed_employee_ids else [],
'views': [[self.env.ref('yuthon_hr_attendance.view_hr_attendance_month_detail_tree').id, 'list'],
[False, 'form']],
}
@@ -358,36 +354,127 @@ class HrAttendanceMonthDetail(models.Model):
compensatory_leave = overtime_summary.compensatory_leave
return work_overtime, compensatory_leave
def _get_hr_leave_type(self, employee, type_list):
def _get_hr_leave_type(self, employee_id, type_list):
"""
请假类型判断是否请假
:param employee: 员工
:param type_list: 请假类型列表
:return: 天,小时
:return: 天
"""
self.ensure_one()
days = calendar.monthrange(int(self.current_year), int(self.current_month))[1]
begin_of_month = datetime.date(int(self.current_year), int(self.current_month), 1)
end_of_month = datetime.date(int(self.current_year), int(self.current_month), days)
date_start = fields.Datetime.from_string(fields.Date.to_date(begin_of_month).strftime('%Y-%m-%d 00:00:00')) - datetime.timedelta(hours=8)
date_end = fields.Datetime.from_string(fields.Date.to_date(end_of_month).strftime('%Y-%m-%d 23:59:59')) - datetime.timedelta(hours=8)
domain = [
('employee_id', '=', employee.id),
('date_from', '<=', date_end),
('date_to', '>=', date_start),
('date_to', '>=', date_start)
]
if type_list:
domain.append(('holiday_status_id.name', 'in', type_list))
hr_leaves = self.env['hr.leave'].sudo().search(domain)
hours = 0.0
days = 0.0
for hr_leave in hr_leaves:
if hr_leave.leave_type_request_unit == 'hour':
hours += float_round(hr_leave.number_of_hours_display, precision_digits=2)
leave_days = {}
year = int(self.current_year)
month = int(self.current_month)
if month == 12:
date_to = date(year, 12, 31)
else:
next_month = month + 1
date_to = date(year, next_month, 1) - datetime.timedelta(days=1)
date_from = date(year, month, 1)
for leave_type in type_list:
holiday_status_id = self.env['hr.leave.type'].search([('name', '=', leave_type)])
leave_records = self.env['hr.leave'].search([
('employee_id', '=', employee_id.id),
('holiday_status_id', '=', holiday_status_id.id),
('date_from', '<', date_to),
('date_to', '>', date_from),
('state', '=', 'validate')
])
leave_days[leave_type] = 0
for leave in leave_records:
leave_start = leave.date_from.date()
leave_end = leave.date_to.date()
start_in_month = max(leave_start, date_from)
end_in_month = min(leave_end, date_to)
# 判断是否跨月
is_cross_month = leave_start.year != leave_end.year or leave_start.month != leave_end.month
if is_cross_month:
if leave_start.year == year and leave_start.month == month:
start_date = leave_start
if leave_end.year == year and leave_end.month == month:
end_date = leave_end
else:
end_date = date(year, month + 1, 1) if month < 12 else date(year + 1, 1, 1)
end_date = end_date - datetime.timedelta(days=1) # 取到当月最后一天
elif leave_end.year == year and leave_end.month == month:
start_date = date(year, month, 1)
end_date = leave_end
else:
start_date = start_in_month
end_date = end_in_month
days = self._compute_duration(holiday_status_id, start_date, end_date)
else:
days = self._compute_duration(holiday_status_id, start_in_month, end_in_month)
if leave_type in leave_days:
leave_days[leave_type] += days
else:
leave_days[leave_type] = days
if leave_type not in leave_days:
leave_days[leave_type] = 0
return leave_days
def _compute_duration(self, holiday_status_id, start_date, end_date):
"""
计算请假时长
:param holiday_status_id: 请假类型
:param start_date: 开始时间
:param end_date: 结束时间
:return:天
"""
# 如果是自然日:
if holiday_status_id.type_of_work == "1":
if start_date and end_date:
days = (end_date - start_date).days + 1
else:
days += float_round(hr_leave.number_of_days_display, precision_digits=2)
return days, hours
days = 0
return days
if holiday_status_id.type_of_work == 0:
calendar_leaves = self.env['resource.calendar.leaves'].search([("calendar_id", "=", "行政考勤工作日")])
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]
days = 0 # 初始化工作日计数
current_date = start_date
while current_date <= end_date:
current_date_str = current_date.strftime('%m月%d日')
current_date_str = self.remove_leading_zero(current_date_str)
is_weekend = current_date.weekday() in [5, 6]
if current_date_str in holidays_list:
# 如果是法定节假日,不计为工作日
pass
elif current_date_str in shift_days_list:
# 如果是调休日,无论是否周末都算作工作日
days += 1
elif not is_weekend:
# 如果不是周末且不是节假日,算作工作日
days += 1
current_date += timedelta(days=1)
else:
delta = end_date - start_date
days = delta.days + 1
return days
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}日'
def analysis_attendance_month_detail(self):
"""
@@ -403,8 +490,8 @@ class HrAttendanceMonthDetail(models.Model):
# 迟到, 早退, 缺卡
self.come_late, self.is_early_card, self.is_no_card = self._get_come_late(self.employee_id)
# 无薪假:hr.leave表
days, hours = self._get_hr_leave_type(self.employee_id, ['事假', '年假'])
self.unpaid_leave = hours
days, = self._get_hr_leave_type(self.employee_id, ['事假', '年假'])
self.unpaid_leave = days
# 行政的进行统计
if not self.employee_id.default_planning_role_id:
@@ -412,8 +499,10 @@ class HrAttendanceMonthDetail(models.Model):
# 迟到, 早退, 缺卡
self.come_late, self.is_early_card, self.is_no_card = self._get_come_late(self.employee_id)
# 无薪假:hr.leave表
days, hours = self._get_hr_leave_type(self.employee_id, ['事假', '年假'])
self.unpaid_leave = hours
leave_days = self._get_hr_leave_type(self.employee_id,['事假', '年假', '产假'])
self.leave_of_absence = leave_days.get('事假', 0)
self.annual_leave = leave_days.get('年假', 0)
self.maternity_leave = leave_days.get('产假', 0)
return True
@@ -650,7 +739,6 @@ class HrAttendanceMonthDetail(models.Model):
获取出勤月度汇总记录结果表
:return: obj
"""
self.ensure_one()
attendance_month_obj = self.env["hr.attendance.month"].search([
('current_year', '=', self.current_year),
('current_month', '=', self.current_month)
@@ -665,29 +753,28 @@ class HrAttendanceMonthDetail(models.Model):
})
return attendance_month_obj
def _get_attendance_month_detail(self, employee):
def _get_attendance_month_detail(self, employee, current_year, current_month):
"""
判断当前是否已经创建了考勤明细
:param employee: 员工
:return: 考情明细
"""
self.ensure_one()
attendance_month_detail_obj = self.env["hr.attendance.month.detail"].search(
[('employee_id', '=', employee.id),
('current_year', '=', self.current_year),
('current_month', '=', self.current_month),
('current_year', '=', current_year),
('current_month', '=', current_month),
], limit=1)
if not attendance_month_detail_obj:
attendance_month_detail_obj = self.env["hr.attendance.month.detail"].sudo().create({
"employee_id": employee.id,
"attendance_year_id": self.attendance_year_id.id,
"current_year": self.current_year,
"current_month": self.current_month,
"current_year": current_year,
"current_month": current_month,
})
return attendance_month_detail_obj
def button_compute_all(self):
self.ensure_one()
processed_employee_ids = []
attendance_month_obj = self._get_attendance_month_obj()
# 查询所有的当前员工? 是否需要判断当前员工是否关联企业微信??预留
calendars = self.env['resource.calendar'].search([])
@@ -699,15 +786,17 @@ class HrAttendanceMonthDetail(models.Model):
for detail_id in detail_ids:
if detail_id.employee_id not in all_employee_ids:
# 判断当前是否已经创建了考勤明细
attendance_month_detail_obj = self._get_attendance_month_detail(detail_id.employee_id)
attendance_month_detail_obj = self._get_attendance_month_detail(detail_id.employee_id, detail_id.current_year, detail_id.current_month)
attendance_month_detail_obj.hr_attendance_month_id = attendance_month_obj.id
# 判断当前用户是否已经确认签字,签字后不再更新
if attendance_month_detail_obj.state == 'confirmed' and attendance_month_detail_obj.signature:
continue
# 更新用户的考勤明细
attendance_month_detail_obj.analysis_attendance_month_detail()
# 返回action动作
action = self._hr_attendance_month_detail_action()
if detail_id.employee_id.id not in processed_employee_ids:
processed_employee_ids.append(detail_id.employee_id.id)
# 返回包含处理过的员工ID的action动作
action = self._hr_attendance_month_detail_action(processed_employee_ids)
return action
def attendance_month_detail(self):
@@ -88,6 +88,7 @@ class HrAttendanceMonthWizard(models.TransientModel):
"current_month": self.current_month,
},
'domain': [
('company_id', '=', self.company_id.id),
('current_year', '=', self.current_year),
('current_month', '=', self.current_month),
],
@@ -115,7 +116,7 @@ class HrAttendanceMonthWizard(models.TransientModel):
self.ensure_one()
attendance_month_obj = self._get_attendance_month_obj()
# 查询所有的当前员工? 是否需要判断当前员工是否关联企业微信??预留
user_obj = self.env['res.users'].sudo().search([])
user_obj = self.env['res.users'].sudo().search([('company_id', '=', self.company_id.id)])
calendars = self.env['resource.calendar'].search([])
all_employee_ids = set()
for calendar in calendars:
@@ -139,7 +140,6 @@ class HrAttendanceMonthWizard(models.TransientModel):
continue
# 更新用户的考勤明细
attendance_month_detail_obj.analysis_attendance_month_detail()
self.get_leave_and_overtime_info(employee.id)
# 返回action动作
action = self._hr_attendance_month_detail_action()
return action
@@ -160,84 +160,6 @@ class HrAttendanceMonthWizard(models.TransientModel):
})
wizard.search_attendance_month()
def get_leave_and_overtime_info(self, employee_id):
leave_types = {
'年假': 'annual_leave',
'事假': 'unpaid_leave',
'补休': 'cop_leave',
'产假': 'maternity_leave',
'病假(普通)': 'general_sick_leave',
'病假(住院期)': 'hospital_sick_leave',
'病假(出院恢复期)': 'recovery_sick_leave',
'陪产假': 'paternity_leave',
'计生假': 'family_planning_leave',
'丧假': 'bereavement_leave',
'探亲假' : 'home_visit_leave',
'婚假': 'marriage_leave',
'考试假': 'exam_leave',
'家长会假': 'parents_meeting_leave',
}
leave_days = {}
year = int(self.current_year)
month = int(self.current_month)
if month == 12:
date_to = date(year, 12, 31)
else:
next_month = month + 1
date_to = date(year, next_month, 1) - datetime.timedelta(days=1)
date_from = date(year, month, 1)
for leave_type in leave_types:
total_days = 0
holiday_status_id = self.env['hr.leave.type'].search([('name', '=', leave_type)])
leave_records = self.env['hr.leave'].search([
('employee_id', '=', employee_id),
('holiday_status_id', '=', holiday_status_id.id),
('date_from', '<', date_to),
('date_to', '>', date_from),
('state', '=', 'validate')
])
for leave in leave_records:
leave_start = leave.date_from.date()
leave_end = leave.date_to.date()
start_in_month = max(leave_start, date_from)
end_in_month = min(leave_end, date_to)
# 判断是否跨月
is_cross_month = leave_start.year != leave_end.year or leave_start.month != leave_end.month
if is_cross_month:
if leave_start.year == year and leave_start.month == month:
start_date = leave_start
if leave_end.year == year and leave_end.month == month:
end_date = leave_end
else:
end_date = date(year, month + 1, 1) if month < 12 else date(year + 1, 1, 1)
end_date = end_date - datetime.timedelta(days=1) # 取到当月最后一天
elif leave_end.year == year and leave_end.month == month:
start_date = date(year, month, 1)
end_date = leave_end
else:
start_date = start_in_month
end_date = end_in_month
days = self._create_leave_record(holiday_status_id, start_date, end_date)
leave_days[leave_type] = days
else:
for leave in leave_records:
total_days += leave.number_of_days_display
leave_days[leave_type] = total_days
employee = self.env['hr.employee'].browse(employee_id)
all_overtime_hours = employee.all_overtime_hours
attendance_detail = self.env['hr.attendance.month.detail'].search([
('employee_id', '=', employee_id),
('current_year', '=', self.current_year),
('current_month', '=', self.current_month)
], limit=1)
for field_name, days in leave_days.items():
setattr(attendance_detail, leave_types[field_name], days)
setattr(attendance_detail, 'work_overtime', all_overtime_hours)
# 创建用户记录传入日期来获取天数
def _create_leave_record(self, holiday_status_id, start_date, end_date):
if holiday_status_id.type_of_work == 0:
@@ -6,7 +6,7 @@
<field name="arch" type="xml">
<form string="考勤统计">
<group>
<field name="company_id" invisible="1"/>
<field name="company_id"/>
<field name="attendance_year_id"/>
<field name="current_month" />
</group>