diff --git a/yuthon_hr_attendance/models/hr_attendance_month.py b/yuthon_hr_attendance/models/hr_attendance_month.py index 68d175a62..995bdacaf 100644 --- a/yuthon_hr_attendance/models/hr_attendance_month.py +++ b/yuthon_hr_attendance/models/hr_attendance_month.py @@ -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): diff --git a/yuthon_hr_attendance/wizard/hr_attendance_month_wizard.py b/yuthon_hr_attendance/wizard/hr_attendance_month_wizard.py index 8576a1006..0d9673b72 100644 --- a/yuthon_hr_attendance/wizard/hr_attendance_month_wizard.py +++ b/yuthon_hr_attendance/wizard/hr_attendance_month_wizard.py @@ -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: diff --git a/yuthon_hr_attendance/wizard/hr_attendance_month_wizard_views.xml b/yuthon_hr_attendance/wizard/hr_attendance_month_wizard_views.xml index 270be32f0..c94ec43e1 100644 --- a/yuthon_hr_attendance/wizard/hr_attendance_month_wizard_views.xml +++ b/yuthon_hr_attendance/wizard/hr_attendance_month_wizard_views.xml @@ -6,7 +6,7 @@
- +