diff --git a/yuthon_holiday/models/inherit_hr_leave.py b/yuthon_holiday/models/inherit_hr_leave.py
index ddcf3af07..cdc39d3ea 100644
--- a/yuthon_holiday/models/inherit_hr_leave.py
+++ b/yuthon_holiday/models/inherit_hr_leave.py
@@ -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:
diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py
index 9a8273f38..2e55da2f6 100644
--- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py
+++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py
@@ -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(
diff --git a/yuthon_hr_employee_approval/views/yuthon_department_change_viwes.xml b/yuthon_hr_employee_approval/views/yuthon_department_change_viwes.xml
index 5313a4a6c..2ec0af638 100644
--- a/yuthon_hr_employee_approval/views/yuthon_department_change_viwes.xml
+++ b/yuthon_hr_employee_approval/views/yuthon_department_change_viwes.xml
@@ -5,9 +5,9 @@
yuthon.department.change
-
-
-
+
+
+