Files
gzth/yuthon_holiday/models/inherit_hr_leave_allocation.py
2026-09-02 12:01:32 +08:00

415 lines
20 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from datetime import date, datetime
import calendar
class HrLeaveAllocation(models.Model):
_inherit = 'hr.leave.allocation'
HOURS_PER_DAY = 8.0
is_reduce_compensatory = fields.Boolean(
string='减少补休',
default=False,
help='标识此补休分配是否为扣减操作。填写负数时自动勾选,写入考勤确认单时走 reduce 逻辑。'
)
@api.depends('holiday_status_id', 'allocation_type', 'number_of_hours_display', 'number_of_days_display', 'date_to')
def _compute_from_holiday_status_id(self):
super()._compute_from_holiday_status_id()
for allocation in self:
if allocation._get_request_unit() == 'hour':
allocation.number_of_days = allocation.number_of_hours_display / self.HOURS_PER_DAY
@api.depends('number_of_days', 'holiday_status_id', 'employee_id', 'holiday_type')
def _compute_number_of_hours_display(self):
for allocation in self:
allocation.number_of_hours_display = allocation.number_of_days * self.HOURS_PER_DAY
holiday_type = fields.Selection([
('employee', '按员工角色'),
('company', 'By Company'),
('department', 'By Department'),
('category', 'By Employee Tag')],
string='模式', readonly=False, required=True, default='employee')
new_employee_holidays = fields.Selection([('limit', '限制'), ('un_limit', '不限制')], string="新员工请假")
holiday_month = fields.Selection(selection=[(str(i), str(i)) for i in range(1, 13)], string="入职月份",
help='入职时长小于此月份时,不可申请此假期')
holiday_expiration_date = fields.Selection([('never', '永久'), ('no_never', '不固定')], string="新员工请假")
remarks = fields.Text(string='备注', tracking=True)
start_time = fields.Date(string="开始时间")
end_time = fields.Date(string="结束时间")
@api.model_create_multi
def create(self, vals_list):
"""拦截补休的负数天数,绕过 SQL CHECK 约束"""
for vals in vals_list:
if vals.get('number_of_days', 0) < 0:
holiday_status_id = vals.get('holiday_status_id')
if holiday_status_id:
status = self.env['hr.leave.type'].browse(holiday_status_id)
if status.name == '补休':
vals['is_reduce_compensatory'] = True
vals['number_of_days'] = abs(vals['number_of_days'])
return super().create(vals_list)
def write(self, vals):
"""拦截补休的负数天数,绕过 SQL CHECK 约束(allocation_type=regular 时 number_of_days 必须 > 0)"""
if vals.get('number_of_days', 0) < 0:
for rec in self:
if rec.holiday_status_id.name == '补休':
vals = dict(vals)
vals['is_reduce_compensatory'] = True
vals['number_of_days'] = abs(vals['number_of_days'])
break
return super().write(vals)
@api.onchange('users_role_ids')
def onchange_employee_ids(self):
users_role_list = []
for i in self.users_role_ids:
user_role_data = self.env['hr.employee'].search(['|', ('user_role_id', '=', i.id.origin), ('user_role_ids', '=', i.id.origin)])
users_role_list += [ro.user_id.id for ro in user_role_data]
if self.users_role_ids:
false_none_list = [x for x in users_role_list if x != False]
self.employee_ids = [(6, 0, list(set(false_none_list)))]
def _action_validate_create_childs(self):
"""
验证分配方案,根据应计方案创建对应用户的年假分配方案
@return:
"""
allocation_vals = []
for allocation in self:
if allocation.allocation_type == 'accrual' and allocation.accrual_plan_id:
# 根据应计方案创建对应用户的年假分配方案
if (allocation.state == 'validate'and (
allocation.holiday_type in ['category', 'department', 'company']
or (allocation.holiday_type == 'employee' and len(allocation.employee_ids) > 1)
)):
if allocation.holiday_type == 'employee':
employees = allocation.employee_ids
elif allocation.holiday_type == 'category':
employees = allocation.category_id.employee_ids
elif allocation.holiday_type == 'department':
employees = allocation.department_id.member_ids
else:
employees = self.env['hr.employee'].search([('company_id', '=', allocation.mode_company_id.id)])
# {员工社保时间: 员工列表}
emp_dict = {}
# 员工根据社保时间创建字典
for e in employees:
key = e.compute_month
if emp_dict.get(key):
emp_dict[key].append(e)
else:
emp_dict[key] = [e]
# 将里程节点按照时间从大到小排序,满足一个以后跳出循环
plan_level = allocation.accrual_plan_id.level_ids.sorted(key=lambda x: x.added_value, reverse=True)
for emp_int, emp_list in emp_dict.items():
for level in plan_level:
level_month = 0
if level.added_value_type == 'day':
level_month = level.added_value // 30
if level.added_value_type == 'hour':
level_month = level.added_value // (30 * 7)
if emp_int != 0 and emp_int >= level_month:
info_list = [{**item, 'number_of_days': level.maximum_leave} for item in allocation._prepare_holiday_values(emp_list)]
allocation_vals.extend(info_list)
break
else:
super()._action_validate_create_childs()
if allocation_vals:
self.env['hr.leave.allocation'].with_context(mail_notify_force_send=False, mail_activity_automation_skip=True).create(allocation_vals)
self.linked_request_ids.filtered(lambda c: c.state != 'validate').action_validate()
def all_refuse(self):
leave_ids = self.env["hr.leave.allocation"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for leave in leave_ids:
leave.write({'state': 'refuse'})
def all_validate(self):
leave_ids = self.env["hr.leave.allocation"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for leave in leave_ids:
leave.write({'state': 'validate'})
def one_time_date_to(self):
leave_ids = self.env["hr.leave.allocation"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for leave in leave_ids:
company_name = leave.employee_id.company_id.name
start_date = leave.date_from
if not start_date:
continue
start_dt = fields.Date.from_string(start_date) if isinstance(start_date, str) else start_date
end_dt = None
if company_name == '集团本部':
target_year = start_dt.year + 1
end_dt = date(target_year, 12, 31)
elif company_name == '天河粮储':
target_year = start_dt.year + 1
end_dt = date(target_year, 6, 30)
elif leave.employee_id.company_id.id == 3:
target_year = start_dt.year + 1
feb_last_day = calendar.monthrange(target_year, 2)[1]
end_dt = date(target_year, 2, feb_last_day)
if end_dt:
leave.write({
'date_to': fields.Date.to_string(end_dt)
})
def one_time_company_id(self):
leave_ids = self.env["hr.leave.allocation"].browse(
self._context.get('active_ids', self._context.get('active_id'))
)
for leave in leave_ids:
holiday_status = self.env['hr.leave.type'].search(
[('company_id', '=', leave.employee_id.company_id.id),
('name', '=', '年假')
],
limit=1
)
leave.write({
'holiday_status_id': holiday_status.id
})
def action_uninstall_wizard(self):
return {
"type": "ir.actions.act_window",
"name": "初始化假期",
"res_model": "leave.allocation.wizard",
"view_mode": "form",
"target": "new",
}
# @api.model
# def create(self, vals):
# allocation = super(HrLeaveAllocation, self).create(vals)
# employee = allocation.employee_id
# hours = allocation.number_of_hours_display
# date_from = allocation.date_from.replace(day=1)
# if allocation.holiday_status_id.name == '年假' and allocation.state == 'validate':
# employee.write({'quota_issued': employee.quota_issued + hours})
# reports_id = self.env['yuthon.vacation.reports'].search([('employee_id', '=', employee.id), ('vacation_date', '=', date_from)], limit=1)
# # 创建假期明细记录
# self.env['vacation.leave.line'].create({
# 'vacation_id': reports_id.id,
# 'update_type': 'add',
# 'update_date': fields.Datetime.now(),
# 'update_lines': hours,
# 'allocation_type': 'allocation',
# 'text': allocation.name
# })
# elif allocation.holiday_status_id.name == '补休':
# reports_id = self.env['yuthon.vacation.reports'].search([
# ('employee_id', '=', employee.id), ('vacation_date', '=', date_from)
# ], limit=1)
# self.env['vacation.over.line'].create({
# 'vacation_id': reports_id.id,
# 'update_type': 'add',
# 'update_date': fields.Datetime.now(),
# 'workday': hours,
# 'legal_day': 0,
# 'public_holidays': 0,
# 'meal_number': 0,
# 'text': allocation.name
# })
# return allocation
def unlink(self):
"""删除记录时同步删除对应的假期明细行"""
for record in self:
# 检查是否有需要同步的员工记录
if record.employee_id and record.holiday_status_id.name == '年假':
employee = record.employee_id
hours = record.number_of_hours_display
lines_to_delete = self.env['vacation.leave.line'].search([
('vacation_id.employee_id', '=', employee.id),
('update_type', '=', 'add'),
('update_lines', '=', hours),
('allocation_type', '=', 'allocation'),
('text', '=', record.name)
])
if lines_to_delete:
lines_to_delete.is_void = True
employee.write({
'quota_issued': employee.quota_issued - hours
})
elif record.employee_id and record.holiday_status_id.name == '补休':
employee = record.employee_id
hours = record.number_of_hours_display
lines_to_delete = self.env['vacation.over.line'].search([
('vacation_id.employee_id', '=', employee.id),
('update_type', '=', 'add'),
('workday', '=', hours),
('text', '=', record.name)
])
if lines_to_delete:
lines_to_delete.is_void = True
return super(HrLeaveAllocation, self).unlink()
def action_refuse(self):
"""拒绝假期分配时,删除之前验证时添加的明细行并重新计算"""
import logging
logger = logging.getLogger(__name__)
result = super(HrLeaveAllocation, self).action_refuse()
for leave in self:
logger.info("===== 拒绝假期分配处理开始 =====")
logger.info(f"假期分配ID: {leave.id}")
logger.info(f"假期类型: {leave.holiday_status_id.name}")
logger.info(f"员工ID: {leave.employee_id.id}, 员工姓名: {leave.employee_id.name}")
logger.info(f"date_from: {leave.date_from}")
logger.info(f"分配类型: {leave.allocation_type}")
# 使用date_from或date_to计算月份
if leave.date_from:
vacation_date = leave.date_from.replace(day=1)
elif leave.date_to:
vacation_date = leave.date_to.replace(day=1)
else:
vacation_date = fields.Date.today().replace(day=1)
logger.info(f"vacation_date: {vacation_date}")
reports_id = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', leave.employee_id.id),
('vacation_date', '=', vacation_date)
], limit=1)
logger.info(f"找到的reports_id: {reports_id}")
if not reports_id:
logger.info("未找到对应的考勤报告记录")
continue
if leave.holiday_status_id.name == '年假':
# 精确查找并删除当前表单添加的年假明细行
lines_to_delete = reports_id.leave_ids.filtered(
lambda l: l.update_type == 'add' and l.allocation_id.id == leave.id
)
logger.info(f"找到需要删除的年假明细行数量: {len(lines_to_delete)}")
if lines_to_delete:
lines_to_delete.unlink()
reports_id.update_line()
logger.info("年假明细行已删除并重新计算")
else:
# 尝试按旧方式查找(没有allocation_id的记录)
old_lines = reports_id.leave_ids.filtered(
lambda l: l.update_type == 'add' and f'({leave.id})' in (l.text or '')
)
logger.info(f"按旧方式找到的明细行数量: {len(old_lines)}")
if old_lines:
old_lines.unlink()
reports_id.update_line()
logger.info("按旧方式删除年假明细行并重新计算")
elif leave.holiday_status_id.name == '补休':
# 精确查找并删除当前表单添加的补休明细行(增加和减少类型都处理)
lines_to_delete = reports_id.overtime_ids.filtered(
lambda o: o.update_type in ('add', 'reduce') and o.allocation_id.id == leave.id
)
logger.info(f"找到需要删除的补休明细行数量: {len(lines_to_delete)}")
if lines_to_delete:
lines_to_delete.unlink()
reports_id.update_line()
logger.info("补休明细行已删除并重新计算")
else:
# 尝试按旧方式查找
old_lines = reports_id.overtime_ids.filtered(
lambda o: o.update_type == 'add' and f'({leave.id})' in (o.text or '')
)
logger.info(f"按旧方式找到的明细行数量: {len(old_lines)}")
if old_lines:
old_lines.unlink()
reports_id.update_line()
logger.info("按旧方式删除补休明细行并重新计算")
logger.info("===== 拒绝假期分配处理结束 =====")
return result
def action_validate(self):
import logging
logger = logging.getLogger(__name__)
result = super(HrLeaveAllocation, self).action_validate()
for leave in self:
if leave.holiday_status_id.name == '年假':
vacation_date = leave.date_from.replace(day=1) if leave.date_from else fields.Date.today().replace(day=1)
reports_id = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', leave.employee_id.id),
('vacation_date', '=', vacation_date)
], limit=1)
if reports_id:
leave_hours = leave.number_of_days_display * 8
reports_id.write({
'valid_date': leave.date_to,
'leave_ids': [(0, 0, {
'vacation_id': reports_id.id,
'allocation_id': leave.id,
'update_type': 'add',
'update_date': fields.Datetime.now(),
'valid_date': leave.date_from,
'update_lines': leave_hours,
'text': leave.name
})]
})
# 验证是否写入成功
new_line = reports_id.leave_ids.filtered(lambda l: l.allocation_id.id == leave.id)
reports_id.update_line()
elif leave.holiday_status_id.name == '补休':
vacation_date = leave.date_from.replace(day=1) if leave.date_from else fields.Date.today().replace(day=1)
reports_id = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', leave.employee_id.id),
('vacation_date', '=', vacation_date)
], limit=1)
if reports_id:
rest_hours = leave.number_of_days_display * 8
# 根据 is_reduce_compensatory 判断增加还是减少
# (number_of_days 已被 write 拦截转为正数以绕过 SQL CHECK 约束)
if leave.is_reduce_compensatory:
update_type = 'reduce'
workday = rest_hours
else:
update_type = 'add'
workday = rest_hours
reports_id.write({
'valid_date1': leave.date_to,
'overtime_ids': [(0, 0, {
'vacation_id': reports_id.id,
'allocation_id': leave.id,
'update_type': update_type,
'update_date': fields.Datetime.now(),
'valid_date': leave.date_to,
'workday': workday,
'text': leave.name or '手动分配'
})]
})
reports_id.update_line()
return result
def update_employee(self):
leave_ids = self.env["hr.leave.allocation"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for leave in leave_ids:
if leave.holiday_status_id.name == '年假':
leave.employee_id.quota_issued = leave.number_of_days