Merge pull request !4364 from 李鹏宇/dev
This commit is contained in:
李鹏宇
2025-08-06 10:45:58 +00:00
committed by Gitee
3 changed files with 320 additions and 64 deletions
+201 -42
View File
@@ -7,6 +7,8 @@ from odoo.exceptions import UserError, ValidationError
import re
from odoo.tools.misc import format_date
from collections import namedtuple, defaultdict
from datetime import date
import calendar
class HrLeave(models.Model):
@@ -198,13 +200,11 @@ class HrLeave(models.Model):
def _process_annual_leave(self, allocation, employees):
"""处理年假逻辑"""
days = allocation.number_of_days * 8
for employee in employees:
# 更新员工年假余额
employee.write({
'user_year': employee.user_year + days
})
reports_id = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', employee.id)
], limit=1)
@@ -212,7 +212,6 @@ class HrLeave(models.Model):
reports_id = self.env['yuthon.vacation.reports'].create({
'employee_id': employee.id,
})
self.env['vacation.leave.line'].create({
'vacation_id': reports_id.id,
'update_type': 'reduce',
@@ -291,7 +290,6 @@ class HrLeave(models.Model):
for holiday in self:
# 如果是自然日:
if holiday.holiday_status_id.type_of_work == "1":
calendar = self.env['resource.calendar'].sudo().search([("name", "=", "行政考勤自然日")], limit=1)
if holiday.request_date_from and holiday.request_date_to:
days = (holiday.request_date_to - holiday.request_date_from).days + 1
hours = days * 8
@@ -300,7 +298,6 @@ class HrLeave(models.Model):
hours = 0.0
holiday.number_of_hours = hours
holiday.number_of_days = days
if holiday.holiday_status_id.type_of_work == "0":
calendar_leaves = self.env['resource.calendar.leaves'].search([("calendar_id", "=", "行政考勤工作日")])
holidays_list = [] # 假期
@@ -370,16 +367,8 @@ class HrLeave(models.Model):
if is_weekend:
days -= 1
current_date += timedelta(days=1)
holiday.number_of_days = 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 _compute_request_unit_hours(self):
if not self.env.context.get('no_change_request_unit_hours'):
super()._compute_request_unit_hours()
@@ -509,52 +498,222 @@ class HolidaysRequestBatch(models.Model):
# 提交
def action_submit(self):
self.leave_ids.action_validate()
self.hour_leave_ids.action_validate()
days = 0
leave_list = []
reports_id = self.env['yuthon.vacation.reports'].search([
"""提交请假申请,区分年假和其他假期处理"""
# self.leave_ids.action_validate()
# self.hour_leave_ids.action_validate()
self._process_specific_leaves('年假', is_annual_leave=True)
other_leave_types = [
'产假', '事假', '法定假', '调休', '补休', '值班调休',
'婚假', '陪产假', '计生假', '丧假', '探亲假',
'家长会假', '病假(普通)', '病假(住院期)',
'病假(出院恢复期)', '考试假'
]
for leave_type in other_leave_types:
self._process_specific_leaves(leave_type, is_annual_leave=False)
return True
def _process_specific_leaves(self, leave_type_name, is_annual_leave):
"""
处理指定类型假期
:param leave_type_name: 假期类型名称
:param is_annual_leave: 是否为年假(True/False)
"""
# 收集天假和小时假记录
day_leaves = self._get_day_leaves(leave_type_name)
hourly_leaves = self._get_hourly_leaves(leave_type_name)
# 处理天假
for leave in day_leaves:
date_ranges = self._split_by_month(leave['start'], leave['end']) if not is_annual_leave else [
{'start': leave['start'], 'end': leave['end']}]
for period in date_ranges:
days = self._compute_duration_day(leave['type_id'], period['start'], period['end'])
if days > 0:
if is_annual_leave:
self._create_annual_leave_line(period['start'], leave, days, period)
else:
self._create_other_leave_line(period['start'], leave, days, period, leave_type_name)
# 处理小时假
for leave in hourly_leaves:
leave_date = leave['date']
days = leave['days']
if days > 0:
if is_annual_leave:
self._create_annual_leave_line(leave_date, leave, days, {'start': leave_date, 'end': leave_date})
else:
self._create_other_leave_line(leave_date, leave, days,{'start': leave_date, 'end': leave_date}, leave_type_name)
def _create_other_leave_line(self, period_start, leave_data, days, period, leave_type_name):
"""为非年假类型创建vacation.other.line记录"""
# 查找关联的假期额度记录
report_month = date(period_start.year, period_start.month, 1)
vacation_report = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', self.users_id.employee_id.id),
])
workflow_record_id = self.env['soong.workflow.record'].search([
('code', '=', self.code)
])
('vacation_date', '>=', fields.Date.to_string(report_month)),
('vacation_date', '<=', fields.Date.to_string(
date(period_start.year, period_start.month,
calendar.monthrange(period_start.year, period_start.month)[1]))
)
], limit=1)
for line in self.hour_leave_ids:
if line.holiday_status_id.name == '年假':
match = re.search(r'(\d+(?:\.\d+)?)', line.number_of_hours_text or '')
days += float(match.group(1)) / 8 if match else 0
for leave in self.leave_ids:
if leave.holiday_status_id.name == '年假':
days += leave.number_of_days
leave_list.append((0, 0, {
'vacation_id': reports_id.id,
self.env['vacation.other.line'].create({
'vacation_id': vacation_report.id if vacation_report else False,
'holiday_type': leave_data['type_id'].id,
'employee_id': self.users_id.employee_id.id,
'workflow_record_id': leave_data['workflow_id'],
'update_date': fields.Date.today(),
'update_type': 'reduce',
'workflow_record_id':workflow_record_id.id,
'update_date': fields.Datetime.now(),
'lines': days,
'text': f"请假审批"
})
def _get_day_leaves(self, leave_type_name):
workflow_id = self.env['soong.workflow.record'].search([('code', '=', self.code)], limit=1).id
return [{
'start': fields.Date.from_string(leave.request_date_from),
'end': fields.Date.from_string(leave.request_date_to),
'type_id': leave.holiday_status_id,
'workflow_id': workflow_id}
for leave in self.leave_ids
if leave.holiday_status_id.name == leave_type_name
and leave.request_date_from
and leave.request_date_to
]
def _get_hourly_leaves(self, leave_type_name):
workflow_id = self.env['soong.workflow.record'].search([('code', '=', self.code)], limit=1).id
hourly_records = []
for line in self.hour_leave_ids:
if line.holiday_status_id.name == leave_type_name and line.request_date_from:
hours_match = re.search(r'(\d+(?:\.\d+)?)', line.number_of_hours_text or '')
hours = float(hours_match.group(1)) if hours_match else 0
hourly_records.append({
'date': fields.Date.from_string(line.request_date_from),
'type_id': line.holiday_status_id,
'workflow_id': workflow_id,
'days': hours / 8 # 转换为天数
})
return hourly_records
def _split_by_month(self, start_date, end_date):
# 跨月拆分逻逻辑
date_ranges = []
if start_date.month == end_date.month and start_date.year == end_date.year:
date_ranges.append({'start': start_date, 'end': end_date})
return date_ranges
first_month_end = date(start_date.year, start_date.month,
calendar.monthrange(start_date.year, start_date.month)[1])
date_ranges.append({'start': start_date, 'end': first_month_end})
current_month = start_date.month + 1
current_year = start_date.year
while (current_year < end_date.year) or (current_year == end_date.year and current_month < end_date.month):
if current_month > 12:
current_month = 1
current_year += 1
month_end = date(current_year, current_month, calendar.monthrange(current_year, current_month)[1])
date_ranges.append({'start': date(current_year, current_month, 1), 'end': month_end})
current_month += 1
date_ranges.append({'start': date(end_date.year, end_date.month, 1), 'end': end_date})
return date_ranges
def _create_annual_leave_line(self, period_start, leave_data, days, period):
"""更新年假记录"""
report_month = date(period_start.year, period_start.month, 1)
vacation_report = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', self.users_id.employee_id.id),
('vacation_date', '>=', fields.Date.to_string(report_month)),
('vacation_date', '<=', fields.Date.to_string(
date(period_start.year, period_start.month,
calendar.monthrange(period_start.year, period_start.month)[1]))
)
], limit=1)
# 创建年假明细记录
self.env['vacation.leave.line'].create({
'vacation_id': vacation_report.id if vacation_report else False,
'employee_id': self.users_id.employee_id.id,
'workflow_record_id': leave_data['workflow_id'],
'update_type': 'reduce',
'update_date': fields.Date.today(),
'update_lines': days,
'text': '审批'
}))
reports_id.write({'leave_ids': leave_list})
'is_void': False,
'allocation_type': 'vacation',
'text': f"请假审批w"
})
def _compute_duration_day(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 = 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}日'
@api.constrains('is_day', 'is_hour', 'leave_ids', 'hour_leave_ids')
def _check_leave_type_and_details(self):
"""检查请假类型和明细行是否符合要求"""
for record in self:
# 验证 is_day 和 is_hour 不能同时为空
if not record.is_day and not record.is_hour:
raise ValidationError(_("请选择按天请假或按小时请假!"))
# 验证 leave_ids 和 hour_leave_ids 不能同时为空
if not record.leave_ids and not record.hour_leave_ids:
raise ValidationError(_("请至少添加一条按天或按小时的请假记录!"))
# 验证按天请假时必须选择按天请假
if record.leave_ids and not record.is_day:
raise ValidationError(_("添加了按天请假记录,必须选择'按天请假'选项!"))
# 验证按小时请假时必须选择按小时请假
if record.hour_leave_ids and not record.is_hour:
raise ValidationError(_("添加了按小时请假记录,必须选择'按小时请假'选项!"))
# 原有的小时数验证逻辑(仅在按小时请假时有小时记录时执行)
if record.hour_leave_ids and not record.leave_ids:
number_hours = 0
for line in record.hour_leave_ids:
@@ -26,26 +26,27 @@ class YuthonVacationReports(models.Model):
working_overtime = fields.Float(string='加班时', compute='_compute_working_overtime', store=True, digits=(16, 3))
meal_fee = fields.Integer(string="误餐次数", compute='_compute_meal_fee', store=True)
medical_fee = fields.Float(string="体检费", compute='_compute_medical_fee', store=True)
compensatory_leave = fields.Integer(string="调休")
leave_of_absence = fields.Integer(string="事假", digits=(16, 3))
statutory_leave = fields.Integer(string="法定假")
cop_leave = fields.Float(string='补休', digits=(16, 3))
duty_off_leave = fields.Integer(string='值班调休')
marriage_leave = fields.Integer(string='婚假')
maternity_leave = fields.Integer(string='产假')
paternity_leave = fields.Integer(string='陪产假')
family_planning_leave = fields.Integer(string='计生假')
bereavement_leave = fields.Float(string='丧假')
home_visit_leave = fields.Integer(string='探亲假')
parents_meeting_leave = fields.Integer(string='家长会假')
general_sick_leave = fields.Float(string='病假(普通)')
hospital_sick_leave = fields.Float(string='病假(住院期)')
recovery_sick_leave = fields.Float(string='病假(出院恢复期)')
exam_leave = fields.Integer(string='考试假')
compensatory_leave = fields.Integer(string="调休", compute='_compute_other_leave', store=True)
leave_of_absence = fields.Float(string="事假", digits=(16, 3), compute='_compute_other_leave', store=True)
statutory_leave = fields.Integer(string="法定假", compute='_compute_other_leave', store=True)
cop_leave = fields.Float(string='补休', digits=(16, 3), compute='_compute_other_leave', store=True)
duty_off_leave = fields.Integer(string='值班调休', compute='_compute_other_leave', store=True)
marriage_leave = fields.Integer(string='婚假', compute='_compute_other_leave', store=True)
maternity_leave = fields.Integer(string='产假', compute='_compute_other_leave', store=True)
paternity_leave = fields.Integer(string='陪产假', compute='_compute_other_leave', store=True)
family_planning_leave = fields.Integer(string='计生假', compute='_compute_other_leave', store=True)
bereavement_leave = fields.Float(string='丧假', compute='_compute_other_leave', store=True)
home_visit_leave = fields.Integer(string='探亲假', compute='_compute_other_leave', store=True)
parents_meeting_leave = fields.Integer(string='家长会假', compute='_compute_other_leave', store=True)
general_sick_leave = fields.Float(string='病假(普通)', digits=(16, 3), compute='_compute_other_leave', store=True)
hospital_sick_leave = fields.Float(string='病假(住院期)', digits=(16, 3), compute='_compute_other_leave', store=True)
recovery_sick_leave = fields.Float(string='病假(出院恢复期)', digits=(16, 3), compute='_compute_other_leave', store=True)
exam_leave = fields.Integer(string='考试假', compute='_compute_other_leave', store=True)
right_days = fields.Float(string='转正天数')
internship_days = fields.Float(string='实习天数')
should_attendance = fields.Integer(string="应到")
actual_attendance = fields.Integer(string="实到")
should_attendance = fields.Integer(string="应到", compute='_compute_attendance_fields', store=True)
actual_attendance = fields.Integer(string="实到", compute='_compute_attendance_fields', store=True)
is_late_card = fields.Integer(string="迟到", compute='_compute_attendance_fields', store=True)
is_early_card = fields.Integer(string="早退", compute='_compute_attendance_fields', store=True)
@@ -72,17 +73,27 @@ class YuthonVacationReports(models.Model):
is_early = 0
is_no = 0
is_work = 0
leave_days = 0
is_abnormal = False
actual_attendance = 0
if rec.employee_id and rec.vacation_date:
year = rec.vacation_date.year
month = rec.vacation_date.month
days_in_month = calendar.monthrange(year, month)[1]
month_start = date(year, month, 1)
month_end = date(year, month, days_in_month)
leave_days = rec._create_leave_record(month_start, month_end)
for day in range(1, days_in_month + 1):
current_date = date(year, month, day)
attendances = self.env["hr.attendance"].sudo().search([
('employee_id', '=', rec.employee_id.id),
("current_data", '=', current_date)
])
if attendances:
has_valid_check = any(att.check_in or att.check_out for att in attendances)
if has_valid_check:
actual_attendance += 1
for att in attendances:
if att.is_late_card:
is_late += 1
@@ -95,11 +106,98 @@ class YuthonVacationReports(models.Model):
if att.is_abnormal:
is_abnormal = True
rec.should_attendance = leave_days
rec.is_late_card = is_late
rec.is_early_card = is_early
rec.is_no_card = is_no
rec.is_work = is_work
rec.is_abnormal = is_abnormal
rec.actual_attendance = actual_attendance
@api.depends('other_fee_ids', 'other_fee_ids.holiday_type', 'other_fee_ids.lines',
'other_fee_ids.is_void', 'other_fee_ids.update_type')
def _compute_other_leave(self):
for rec in self:
# 获取所有未作废且类型为减少的有效假期记录
valid_lines = rec.other_fee_ids.filtered(
lambda line: not line.is_void and line.update_type == 'reduce'
)
leave_mapping = {
'调休': 'compensatory_leave',
'事假': 'leave_of_absence',
'法定假': 'statutory_leave',
'补休': 'cop_leave',
'值班调休': 'duty_off_leave',
'婚假': 'marriage_leave',
'产假': 'maternity_leave',
'陪产假': 'paternity_leave',
'计生假': 'family_planning_leave',
'丧假': 'bereavement_leave',
'探亲假': 'home_visit_leave',
'家长会假': 'parents_meeting_leave',
'病假(普通)': 'general_sick_leave',
'病假(住院期)': 'hospital_sick_leave',
'病假(出院恢复期)': 'recovery_sick_leave',
'考试假': 'exam_leave'
}
# 初始化所有假期字段为0
for field in leave_mapping.values():
setattr(rec, field, 0)
# 按假期类型分组累加天数
for line in valid_lines:
leave_type_name = line.holiday_type.name or ''
if leave_type_name in leave_mapping:
field_name = leave_mapping[leave_type_name]
current_value = getattr(rec, field_name) or 0
setattr(rec, field_name, current_value + line.lines)
int_fields = [
'compensatory_leave', 'statutory_leave', 'duty_off_leave',
'marriage_leave', 'maternity_leave', 'paternity_leave',
'family_planning_leave', 'home_visit_leave',
'parents_meeting_leave', 'exam_leave'
]
for field in int_fields:
setattr(rec, field, getattr(rec, field) or 0)
def _create_leave_record(self, start_date, end_date):
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)
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 button_put(self):
menu_id = self.env['ir.ui.menu'].search([('name', '=', '考勤确认'), ('parent_id', '!=', False)])
@@ -264,7 +362,6 @@ class YuthonVacationReports(models.Model):
leave_hours += line_value
elif line.update_type == 'reduce':
leave_hours -= line_value
record.leave = leave_hours / 8
@api.depends(
@@ -5,9 +5,9 @@
<field name="model">yuthon.department.change</field>
<field name="arch" type="xml">
<tree string="部门变更审批">
<!-- <header>-->
<!-- <button name="approval_end" type="object" string="结束" dclass="oe_highlight"/>-->
<!-- </header>-->
<!-- <header>-->
<!-- <button name="approval_end" type="object" string="结束" dclass="oe_highlight"/>-->
<!-- </header>-->
<field name="code"/>
<field name="name"/>
</tree>