415 lines
20 KiB
Python
415 lines
20 KiB
Python
# -*- 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
|
||
|