1411 lines
68 KiB
Python
1411 lines
68 KiB
Python
# -*- coding: utf-8 -*-
|
||
|
||
from dateutil.relativedelta import relativedelta
|
||
from pypinyin import pinyin, lazy_pinyin, STYLE_FIRST_LETTER
|
||
from odoo.osv import expression
|
||
from odoo import api, fields, models
|
||
from datetime import date, datetime
|
||
from odoo.exceptions import UserError, ValidationError
|
||
import math
|
||
from datetime import timedelta
|
||
|
||
nation_list = [('a', '汉族'), ('b', '壮族'), ('v', '满族'), ('d', '回族'), ('e', '苗族'), ('f', '维吾尔族'),
|
||
('g', '土家族'), ('h', '彝族'), ('i', '蒙古族'), ('j', '藏族'), ('k', '布依族'), ('l', '侗族'),
|
||
('m', '瑶族'), ('n', '朝鲜族'), ('o', '白族'), ('p', '哈尼族'), ('q', '哈萨克族'), ('r', '黎族'),
|
||
('s', '傣族'), ('t', '畲族'), ('u', '傈僳族'), ('v', '仡佬族'), ('w', '东乡族'), ('x', '拉祜族'),
|
||
('y', '水族'), ('z', '佤族'), ('a1', '纳西族'), ('b1', '羌族'), ('c1', '仫佬族'), ('d1', '锡伯族'),
|
||
('e1', '柯尔克孜族'), ('f1', '达斡尔族'), ('g1', '景颇族'), ('h1', '毛南族'), ('i1', '撒拉族'),
|
||
('j1', '布朗族'), ('k1', '塔吉克族'), ('l1', '阿昌族'), ('m1', '普米族'), ('n1', '鄂温克族'), ('o1', '怒族'),
|
||
('p1', '京族'), ('q1', '基诺族'), ('r1', '德昂族'), ('s1', '保安族'), ('t1', '俄罗斯族'), ('u1', '裕固族'),
|
||
('v1', '乌孜别克族'), ('w1', '门巴族'), ('x1', '鄂伦春族'), ('y1', '独龙族'), ('z1', '塔塔尔族'), ('a2', '赫哲族'),
|
||
('b2', '高山族'), ('c2', '珞巴族'), ('d2', '壮族')]
|
||
|
||
|
||
class YuthonHrEmployee(models.Model):
|
||
"""
|
||
对原模型数据的隐藏,字段的开发和添加
|
||
"""
|
||
_inherit = "hr.employee"
|
||
_order = 'number asc'
|
||
|
||
# name = fields.CharWithPinyin(default_export_compatible=True)
|
||
user_role_ids_changed = fields.Boolean(string="xxx") # bug
|
||
compress_image = fields.Binary(string="压缩头像", attachment=True)
|
||
wecom_userid = fields.Char(string="成员UserID", related="user_id.oauth_uid")
|
||
company_ids = fields.Many2many('res.company', related="user_id.company_ids")
|
||
role_allowed_company_ids = fields.Many2many('res.company', compute='_compute_role_allowed_company_ids')
|
||
|
||
@api.depends('company_ids')
|
||
def _compute_role_allowed_company_ids(self):
|
||
thtz = self.env['res.company'].search([('name', '=', '天河投资集团')], limit=1)
|
||
for rec in self:
|
||
companies = rec.company_ids
|
||
if thtz and thtz not in companies:
|
||
companies |= thtz
|
||
rec.role_allowed_company_ids = companies
|
||
|
||
number = fields.Char(string="序号", store=True)
|
||
is_soldiers = fields.Boolean(string="是否为退伍军人")
|
||
user_number = fields.Char(string="员工编号", default=lambda self: self.env['ir.sequence'].next_by_code('employee_code'))
|
||
use_user_type = fields.Selection([('formal_one', '正式合同(一签)'), ('labor', '劳务协议'), ('formal_two', '正式合同(二签)'),
|
||
('formal_none', '正式合同(无固定)'),
|
||
('part_time', '兼职协议'), ('employment', '用工协议'), ('internship', '实习协议')], string="用工形式")
|
||
contract_type_id = fields.Many2one(related='contract_id.contract_type_id', string="用工形式")
|
||
date_end = fields.Date(string="合同结束日期", compute='_compute_date_end')
|
||
# version = fields.Char(related='contract_id.version', string="版本")
|
||
end_comparison = fields.Selection([('red', '小于30天'), ('yellow', '小于60天'), ('blue', '小于90天')], string="结束时间对比(颜色)",
|
||
compute='_compute_end_comparison', store=True)
|
||
|
||
# 个人信息
|
||
user_salary = fields.Float(string="基本工资")
|
||
home_address = fields.Char(string="家庭住址")
|
||
job_lv_salary = fields.Float(string="职级工资")
|
||
user_sex = fields.Selection([('girl', '女'), ('boy', '男')], string="性别")
|
||
user_state = fields.Selection([('in', '在职'), ('leave', '离职'), ('withdraw', '退休'), ('borrow', '借调'),
|
||
('expatriate', '外派'), ('adjunct', '在职(兼职)')],
|
||
string="员工状态", default='in')
|
||
user_number_start_time = fields.Date(string="身份证开始时间")
|
||
user_role_id = fields.Many2one('soong.user.role', string="主角色")
|
||
user_role_ids = fields.Many2many('soong.user.role', string="辅助角色")
|
||
role_group_ids = fields.Many2many('soong.user.role.group', string='角色组')
|
||
department_ids = fields.Many2many('hr.department', string="辅助部门")
|
||
user_number_end_time = fields.Date(string="身份证到期时间")
|
||
is_identification = fields.Boolean(string="是否填写身份证")
|
||
card_documents_id = fields.Many2one('ir.attachment', string="身份证正面")
|
||
card_back_documents_id = fields.Many2one('ir.attachment', string="身份证反面")
|
||
hukou_documents_id = fields.Many2one('ir.attachment', string="户口本")
|
||
marriage_documents_id = fields.Many2one('ir.attachment', string="结婚证")
|
||
nation = fields.Selection(nation_list, string="民族")
|
||
old = fields.Integer(string="年龄", compute='_compute_info', store=True)
|
||
birthday = fields.Date(string="出生年月", compute='_compute_info', tracking=False)
|
||
birthday_month = fields.Integer(string="生日月份", compute='_compute_info')
|
||
user_type = fields.Char(string="人员类别")
|
||
retire_date = fields.Date(string="退休日期")
|
||
retire_years = fields.Integer(string="离退休年限", compute='_compute_retire_years')
|
||
political_aspects = fields.Selection([('cpc_dang', '中共党员'), ('nld', '民盟'),
|
||
('league_member', '共青团员'), ('people', '群众')], string="政治面貌")
|
||
dang_data = fields.Date(string="入团(党)时间")
|
||
user_origin = fields.Char(string="籍贯")
|
||
user_location = fields.Char(string="户口所在地")
|
||
is_location = fields.Boolean(string="是否本地")
|
||
bank_account = fields.Char(string="银行卡号")
|
||
user_location_type = fields.Selection([('formal', '本地农业'), ('labor', '本地非农业'),
|
||
('part_time', '外地农业'), ('practice', '外地非农业')], string="户籍类型")
|
||
is_local_location = fields.Char(
|
||
string="是否外地",
|
||
compute='_compute_is_local_location',
|
||
)
|
||
|
||
@api.depends('user_location_type')
|
||
def _compute_is_local_location(self):
|
||
for rec in self:
|
||
if rec.user_location_type in ('formal', 'labor'):
|
||
rec.is_local_location = '否'
|
||
else:
|
||
rec.is_local_location = '是'
|
||
duty_replacement_card = fields.Integer(string="值班补卡次数(月)")
|
||
# 工作信息
|
||
entry_date = fields.Date(string="入职日期")
|
||
confirmation_date = fields.Date(string="转正日期")
|
||
company_old = fields.Char(string="司龄", compute='_compute_company_old')
|
||
company_old_year = fields.Integer(string="司龄(年)", compute='_compute_company_old')
|
||
regularization_year = fields.Integer(string="转正(年)", compute='_compute_regularization_year')
|
||
open_start_time = fields.Date(string="参加工作时间")
|
||
is_membership = fields.Boolean(string="工会会员")
|
||
cumulative_work_month = fields.Integer(string="累计工作月数", compute='_compute_social_insurance_info')
|
||
cumulative_work_years = fields.Char(string="累计工作年限", compute='_compute_cumulative_work_years')
|
||
interrupt_work_month = fields.Integer(string="中断工作月数")
|
||
compute_char = fields.Char(string="工龄", compute='_compute_social_insurance_info')
|
||
compute_month = fields.Integer(string="工龄(月)")
|
||
social_insurance_date = fields.Date(string="缴纳社保日期")
|
||
social_insurance_time = fields.Char(string="社保时长", compute='_compute_social_insurance_info', store=True)
|
||
|
||
# 教育
|
||
high_degree = fields.Char(string="最高学历")
|
||
high_degree2 = fields.Char(string="最高学位")
|
||
graduate_school = fields.Char(string="毕业院校")
|
||
profession = fields.Char(string="专业")
|
||
graduate_date = fields.Date(string="毕业时间")
|
||
job_lv_date = fields.Char(string="职级等级/签发日期(人事员)")
|
||
job_lv = fields.Char(string="专业技术/技能等级(劳动局)")
|
||
job_qualification_date = fields.Date(string="职业资格/考取日期")
|
||
job_lv_change = fields.Char(string="职级变动")
|
||
move_note_date = fields.Text(string="异动情况/移动日期")
|
||
# 紧急
|
||
emergency_contacts_name = fields.Char(string="称谓")
|
||
# 其他
|
||
archiving_agency = fields.Char(string="存档机构")
|
||
over_data_time = fields.Datetime(string="最后更新数据时间")
|
||
# 工作信息 -- page
|
||
normal_overtime_hours = fields.Float(string="本月正常加班时间(h)")
|
||
weekend_overtime_hours = fields.Float(string="本月周末加班时间(h)")
|
||
holiday_overtime_hours = fields.Float(string="本月节假日加班时间(h)")
|
||
normal_overtime_hours1 = fields.Float(string="上月正常加班时间(h)")
|
||
weekend_overtime_hours1 = fields.Float(string="上月周末加班时间(h)")
|
||
holiday_overtime_hours1 = fields.Float(string="上月节假日加班时间(h)")
|
||
overtime_this_month = fields.Float(string="本月加班时", compute='_compute_overtime_this_month', store=True)
|
||
overtime_last_month = fields.Float(string="上月加班时")
|
||
result_overtime = fields.Float(string="结算时")
|
||
holiday_hours = fields.Float(string="假期额度")
|
||
all_overtime_hours = fields.Float(string="总加班小时", compute="_compute_all_overtime_hours", store=True)
|
||
replacement_card_count = fields.Integer(string="补卡次数")
|
||
is_annual_salary = fields.Boolean(string="是否年薪")
|
||
is_accum_salary = fields.Boolean(string="是否公积金")
|
||
# 假期
|
||
user_year= fields.Float(string="已用年假")
|
||
remainder_year = fields.Float(string="剩余年假", compute="_compute_remainder_year", store=True)
|
||
cumulative_hours = fields.Float(string="累计剩余探亲假")
|
||
all_year = fields.Float(string="年假总数(小时)")
|
||
previous_balance = fields.Float(string="上年结余")
|
||
quota_issued = fields.Float(string="本年发放额度")
|
||
deadline = fields.Date(string="截止日期")
|
||
late_allowed = fields.Float(string="允许迟到次数")
|
||
|
||
# 其他信息
|
||
employment_registration = fields.Text(string="就业登记办理情况")
|
||
seniority_prize_pick_up_status = fields.Text(string="工龄相关年功奖领取情况")
|
||
|
||
# 考勤机信息
|
||
att_lot_ids = fields.One2many('attendance.iot.user', 'employee_id', string="考勤机信息")
|
||
|
||
# 紧急联系人
|
||
emergency_contact_ids = fields.One2many('yuthon.emergency.contact.line', 'employee_id', string="紧急联系人明细行")
|
||
# 工作经历
|
||
work_experience_ids = fields.One2many('yuthon.work.experience.line', 'employee_id', string="工作经历明细行")
|
||
# 教育经历
|
||
education_experience_ids = fields.One2many('yuthon.education.experience.line', 'employee_id', string="教育经历明细行")
|
||
# 技能证书管理
|
||
skills_certificates_ids = fields.One2many('yuthon.skills.certificates.line', 'employee_id', string="教育经历明细行")
|
||
# 专项扣除
|
||
care_line_ids = fields.One2many('child.care.line', 'employee_id', string="婴幼儿照护明细行")
|
||
education_line_ids = fields.One2many('child.education.line', 'employee_id', string="子女教育明细行")
|
||
support_line_ids = fields.One2many('elderly.support.line', 'employee_id', string="赡养老人明细行")
|
||
hospital_name = fields.Char(string="姓名")
|
||
hospital_related = fields.Char(string="与纳税人关系")
|
||
original_amount = fields.Float(string='个人负担金额')
|
||
|
||
education_name = fields.Char(string="姓名")
|
||
education_end_date = fields.Date(string="结束时间")
|
||
education_deduction = fields.Float(string='扣除金额')
|
||
|
||
loan_name = fields.Char(string="姓名")
|
||
loan_end_date = fields.Date(string="结束时间")
|
||
loan_deduction = fields.Float(string='扣除金额')
|
||
|
||
rent_name = fields.Char(string="姓名")
|
||
rent_end_date = fields.Date(string="结束时间")
|
||
rent_deduction = fields.Float(string='扣除金额')
|
||
|
||
meal_number = fields.Integer(string="误餐费次数")
|
||
meal_supplement = fields.Float(string="餐补")
|
||
|
||
pinyin_name = fields.Char(string="拼音名称", compute='_compute_pinyin', index=True, store=True)
|
||
pinyin_min = fields.Char(string='拼音缩写', compute='_compute_pinyin', index=True, store=True)
|
||
|
||
# 工资计算分组
|
||
salary_group_id = fields.Many2one('yuthon.salary.group', string="工资单分组", store=True)
|
||
salary_date = fields.Date(string="薪资计算时间", store=True)
|
||
salary_month = fields.Selection([('1', '一月'), ('2', '二月'), ('3', '三月'), ('4', '四月'),
|
||
('5', '五月'), ('6', '六月'), ('7', '七月'), ('8', '八月'),
|
||
('9', '九月'), ('10', '十月'), ('11', '十一月'),
|
||
('12', '十二月')], string='薪资计算月份')
|
||
salary_year_jin_ids = fields.Many2many(
|
||
'hr.employee',
|
||
relation='hr_employee_salary_year_jin_rel',
|
||
column1='employee_id',
|
||
column2='related_employee_id',
|
||
string='薪资计算年金人员'
|
||
)
|
||
salary_year = fields.Integer(compute='_compute_salary_year', store=True)
|
||
is_create = fields.Boolean(string='是否已创建转正流程审批')
|
||
last_year_salary = fields.Float(string="年金基数")
|
||
converted = fields.Float(string="折算实际系数")
|
||
conversion_factor = fields.Float(string="上年有效计算")
|
||
cumulative_year = fields.Float()
|
||
total_coefficients = fields.Float(string="系数合计", compute='_compute_total_coefficients', store=True)
|
||
calculation_totals = fields.Float(string="有效计算合计", compute='_compute_total_coefficients', store=True)
|
||
efficient_calculations = fields.Float(string="有效计算")
|
||
social_base_on = fields.Float(string="1-6月公积金基数")
|
||
social_base_off = fields.Float(string="7-12月公积金基数")
|
||
is_salary = fields.Boolean(string="是否薪资计算", default=True)
|
||
# 薪资计算 基数
|
||
salary_medical_insurance = fields.Float(string='薪资医疗费基数', compute='_compute_insurance_salary')
|
||
salary_insurance = fields.Float(string='薪资养老费基数', compute='_compute_insurance_salary')
|
||
salary_work_injury_insurance = fields.Float(string='薪资保险金基数', compute='_compute_insurance_salary')
|
||
salary_housing_fund = fields.Float(string='薪资公积金基数', compute='_compute_insurance_salary')
|
||
# 显示基数
|
||
medical_insurance_base = fields.Float(string='基本医疗费基数', compute='_compute_insurance_bases')
|
||
pension_insurance_base = fields.Float(string='基本养老费基数', compute='_compute_insurance_bases')
|
||
work_injury_insurance_base = fields.Float(string='失业保险金基数', compute='_compute_insurance_bases')
|
||
housing_fund_base = fields.Float(string='住房公积金基数', compute='_compute_insurance_bases')
|
||
is_company = fields.Boolean(string="本公司", default=True, compute='_compute_is_company', search='_search_part_of_company')
|
||
department_id = fields.Many2one(related='contract_id.department_id', string="部门", store=True)
|
||
job_id = fields.Many2one(related='contract_id.job_id', string="工作岗位", store=True)
|
||
resource_calendar_id = fields.Many2one(compute='_compute_resource_calendar_id', string="工作时间", store=True)
|
||
|
||
subsidy_amount = fields.Float(string="岗位补贴金额", compute='_compute_subsidy_amount', store=True)
|
||
security_amount = fields.Float(string="安全委员会金额", compute='_compute_subsidy_amount', store=True)
|
||
quality_inspector_amount = fields.Float(string="质检员金额", compute='_compute_subsidy_amount', store=True)
|
||
|
||
last_month_base_salary_deduction = fields.Float(string="上月基本薪资扣款")
|
||
last_month_performance_deduction = fields.Float(string="上月绩效扣款")
|
||
last_month_commuting_deduction = fields.Float(string='上月通信扣款')
|
||
last_attendance = fields.Boolean(string="上月全勤", store=True)
|
||
last_month_overtime = fields.Float(string="上月加班时数")
|
||
|
||
# 请假记录
|
||
done_leave = fields.Float(string='年假')
|
||
done_working_overtime = fields.Float(string='加班时')
|
||
compensatory_leave = fields.Integer(string="调休")
|
||
leave_of_absence = fields.Float(string="事假")
|
||
statutory_leave = fields.Integer(string="加班时(法定)")
|
||
cop_leave = fields.Float(string='补休')
|
||
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='考试假')
|
||
|
||
marital = fields.Selection([
|
||
('single', '未婚'),
|
||
('married', 'Married'),
|
||
('cohabitant', 'Legal Cohabitant'),
|
||
('widower', 'Widower'),
|
||
('divorced', 'Divorced')
|
||
], string='Marital Status', groups="hr.group_hr_user", default='single', tracking=True)
|
||
|
||
def get_contract_count(self):
|
||
return self.env['hr.employee'].search_count([('end_comparison', 'in', ['red', 'yellow', 'blue'])])
|
||
|
||
def update_role_group_ids(self):
|
||
employee_ids = self.env["hr.employee"].browse(
|
||
self._context.get("active_ids", self._context.get("active_id"))
|
||
)
|
||
for employee in employee_ids:
|
||
employee._onchange_user_roles()
|
||
|
||
def task_contract(self):
|
||
today = fields.Date.today()
|
||
contract_employees = self.search([('end_comparison', 'in', ['red', 'yellow', 'blue'])])
|
||
menu_id = self.env['ir.ui.menu'].search([('name', '=', '合同')], limit=1)
|
||
hr_specialists = self.env['soong.user.role'].search([
|
||
('name', '=', '人事专员'),
|
||
'|', ('company_id.name', 'in', ['集团本部', '天河粮储']), ('company_id', '=', 3)
|
||
])
|
||
# 构建「公司名称: 人事专员wecom_userid」映射
|
||
company_hr_map = {}
|
||
for role in hr_specialists:
|
||
company_name = role.company_id.name
|
||
if role.personnel_ids and len(role.personnel_ids) > 0:
|
||
hr_wecom_id = role.personnel_ids[0].wecom_userid # 取第一个人事专员的企微ID
|
||
company_hr_map[company_name] = hr_wecom_id
|
||
|
||
for employee in contract_employees:
|
||
if not employee.contract_id or not employee.contract_id.date_end:
|
||
continue
|
||
|
||
end_date = employee.contract_id.date_end
|
||
delta = relativedelta(end_date, today)
|
||
total_months = delta.years * 12 + delta.months
|
||
remaining_days = delta.days
|
||
remind_flag = False
|
||
remind_desc = ""
|
||
|
||
if end_date == today:
|
||
remind_flag = True
|
||
remind_desc = f"员工【{employee.name}】的合同今日到期(到期日:{end_date}),请尽快处理"
|
||
elif total_months == 3 and remaining_days == 0:
|
||
remind_flag = True
|
||
remind_desc = f"员工【{employee.name}】的合同还有3个月到期,到期日为{end_date},请尽快处理"
|
||
elif total_months == 2 and remaining_days == 0:
|
||
remind_flag = True
|
||
remind_desc = f"员工【{employee.name}】的合同还有2个月到期,到期日为{end_date},请尽快处理"
|
||
elif total_months == 1 and remaining_days == 0:
|
||
remind_flag = True
|
||
remind_desc = f"员工【{employee.name}】的合同还有1个月到期,到期日为{end_date},请尽快处理"
|
||
|
||
if remind_flag:
|
||
employee_company_name = employee.company_id.name if employee.company_id else ""
|
||
hr_wecom_id = company_hr_map.get(employee_company_name)
|
||
target_url = f'https://oa.thtzjt.com/web#action=509&model=hr.contract&view_type=list&menu_id=11009&cids=2-1-3-4'
|
||
url = self.sudo().env["wecom.apps"].generate_oauth_login_url(target_url, employee.user_id.id if employee.user_id else 0)
|
||
self.sudo().env["wecom.apps"].sync_send_message_textcard(
|
||
category='oa',
|
||
user_id=hr_wecom_id,
|
||
title="合同到期提醒",
|
||
description=remind_desc,
|
||
url=url,
|
||
btntxt="详细信息"
|
||
)
|
||
|
||
|
||
@api.onchange('user_role_id', 'user_role_ids')
|
||
def _onchange_user_roles(self):
|
||
selected_role_ids = []
|
||
if self.user_role_id:
|
||
selected_role_ids.append(self.user_role_id.id)
|
||
|
||
if self.user_role_ids:
|
||
selected_role_ids += self.user_role_ids.ids
|
||
if not selected_role_ids:
|
||
self.role_group_ids = [(5,)]
|
||
return
|
||
role_groups = self.env['soong.user.role.group'].search([
|
||
('user_role_ids', 'in', selected_role_ids)
|
||
])
|
||
if role_groups:
|
||
self.role_group_ids = [(6, 0, role_groups.ids)]
|
||
else:
|
||
self.role_group_ids = [(5,)]
|
||
|
||
@api.constrains('user_role_id', 'user_role_ids', 'role_group_ids')
|
||
def _sync_roles_to_res_groups(self):
|
||
"""
|
||
当员工的主角色(user_role_id)、辅助角色(user_role_ids)或角色组(role_group_ids)发生变更时,
|
||
仅将该员工的关联用户写入对应权限组的用户列表,不修改 res.groups 的角色/角色组字段配置。
|
||
匹配规则:
|
||
- 员工的主角色/辅助角色 与 res.groups.user_role_ids 有交集 → 写入该组用户列表
|
||
- 员工的角色组 与 res.groups.users_role_group_ids 有交集 → 写入该组用户列表
|
||
"""
|
||
for employee in self:
|
||
if not employee.user_id:
|
||
continue
|
||
|
||
# 收集该员工所有角色ID(主角色 + 辅助角色)
|
||
role_ids = []
|
||
if employee.user_role_id:
|
||
role_ids.append(employee.user_role_id.id)
|
||
if employee.user_role_ids:
|
||
role_ids += employee.user_role_ids.ids
|
||
|
||
# 收集该员工所有角色组ID
|
||
role_group_ids = employee.role_group_ids.ids
|
||
|
||
# 1. 找到 res.groups.user_role_ids 与员工角色有交集的权限组,仅追加用户
|
||
if role_ids:
|
||
groups_by_role = self.env['res.groups'].search([
|
||
('user_role_ids', 'in', role_ids)
|
||
])
|
||
groups_by_role.write({'users': [(4, employee.user_id.id)]})
|
||
|
||
# 2. 找到 res.groups.users_role_group_ids 与员工角色组有交集的权限组,仅追加用户
|
||
if role_group_ids:
|
||
groups_by_role_group = self.env['res.groups'].search([
|
||
('users_role_group_ids', 'in', role_group_ids)
|
||
])
|
||
groups_by_role_group.write({'users': [(4, employee.user_id.id)]})
|
||
|
||
def write(self, vals):
|
||
"""覆盖 write,user_role_id / user_role_ids 变更时反向同步到角色的 personnel_ids,
|
||
并正向同步到 res.groups 的用户明细行"""
|
||
role_fields = {'user_role_id', 'user_role_ids'}
|
||
if not role_fields.intersection(vals.keys()):
|
||
return super().write(vals)
|
||
# 记录变更前的角色状态
|
||
old_role_id_map = {emp.id: emp.user_role_id.id for emp in self}
|
||
old_role_ids_map = {emp.id: set(emp.user_role_ids.ids) for emp in self}
|
||
result = super().write(vals)
|
||
for emp in self:
|
||
# --- 主角色反向同步 ---
|
||
if 'user_role_id' in vals:
|
||
old_rid = old_role_id_map.get(emp.id)
|
||
new_rid = emp.user_role_id.id if emp.user_role_id else False
|
||
if old_rid and old_rid != new_rid:
|
||
old_role = self.env['soong.user.role'].browse(old_rid)
|
||
if emp.id in old_role.personnel_ids.ids:
|
||
old_role.write({'personnel_ids': [(3, emp.id)]})
|
||
if new_rid and new_rid != old_rid:
|
||
new_role = self.env['soong.user.role'].browse(new_rid)
|
||
if emp.id not in new_role.personnel_ids.ids:
|
||
new_role.write({'personnel_ids': [(4, emp.id)]})
|
||
# --- 辅助角色反向同步 ---
|
||
if 'user_role_ids' in vals:
|
||
old_rids = old_role_ids_map.get(emp.id, set())
|
||
new_rids = set(emp.user_role_ids.ids)
|
||
added = new_rids - old_rids
|
||
removed = old_rids - new_rids
|
||
for rid in added:
|
||
role = self.env['soong.user.role'].browse(rid)
|
||
if emp.id not in role.personnel_ids.ids:
|
||
role.write({'personnel_ids': [(4, emp.id)]})
|
||
for rid in removed:
|
||
role = self.env['soong.user.role'].browse(rid)
|
||
if emp.id in role.personnel_ids.ids:
|
||
role.write({'personnel_ids': [(3, emp.id)]})
|
||
|
||
# --- 权限组同步 ---
|
||
if not emp.user_id:
|
||
continue
|
||
|
||
# 收集当前所有角色ID(主角色 + 辅助角色)
|
||
current_role_ids = set()
|
||
if emp.user_role_id:
|
||
current_role_ids.add(emp.user_role_id.id)
|
||
if emp.user_role_ids:
|
||
current_role_ids.update(emp.user_role_ids.ids)
|
||
|
||
# 收集被移除的角色ID
|
||
removed_role_ids = set()
|
||
if 'user_role_id' in vals:
|
||
old_rid = old_role_id_map.get(emp.id)
|
||
new_rid = emp.user_role_id.id if emp.user_role_id else False
|
||
if old_rid and old_rid != new_rid:
|
||
removed_role_ids.add(old_rid)
|
||
if 'user_role_ids' in vals:
|
||
old_rids_set = old_role_ids_map.get(emp.id, set())
|
||
new_rids_set = set(emp.user_role_ids.ids)
|
||
removed_role_ids.update(old_rids_set - new_rids_set)
|
||
|
||
# 收集被添加的角色ID
|
||
added_role_ids = set()
|
||
if 'user_role_id' in vals:
|
||
old_rid = old_role_id_map.get(emp.id)
|
||
new_rid = emp.user_role_id.id if emp.user_role_id else False
|
||
if new_rid and new_rid != old_rid:
|
||
added_role_ids.add(new_rid)
|
||
if 'user_role_ids' in vals:
|
||
old_rids_set = old_role_ids_map.get(emp.id, set())
|
||
new_rids_set = set(emp.user_role_ids.ids)
|
||
added_role_ids.update(new_rids_set - old_rids_set)
|
||
|
||
# 1. 新增角色对应的权限组:把员工的用户加进去
|
||
if added_role_ids:
|
||
groups_to_add = self.env['res.groups'].sudo().search([
|
||
('user_role_ids', 'in', list(added_role_ids))
|
||
])
|
||
for group in groups_to_add:
|
||
if emp.user_id.id not in group.users.ids:
|
||
group.sudo().write({'users': [(4, emp.user_id.id)]})
|
||
|
||
# 2. 移除角色对应的权限组:若员工在该组不再有任何角色交集,则把用户移除
|
||
if removed_role_ids:
|
||
groups_to_check = self.env['res.groups'].sudo().search([
|
||
('user_role_ids', 'in', list(removed_role_ids))
|
||
])
|
||
for group in groups_to_check:
|
||
group_role_ids = set(group.user_role_ids.ids)
|
||
if not (group_role_ids & current_role_ids):
|
||
if emp.user_id.id in group.users.ids:
|
||
group.sudo().write({'users': [(3, emp.user_id.id)]})
|
||
return result
|
||
|
||
def update_trade_union(self):
|
||
records = self.env['hr.employee'].search([('is_membership', '=', True)])
|
||
for record in records:
|
||
if record.contract_id.termination_date:
|
||
if record.contract_id.termination_date <= date.today() + timedelta(days=8):
|
||
record.is_membership = False
|
||
if record.contract_id.date_end <= date.today() + timedelta(days=8):
|
||
record.is_membership = False
|
||
|
||
def update_end_comparison(self):
|
||
employees = self.env['hr.employee'].search([])
|
||
for employee in employees:
|
||
employee._compute_end_comparison()
|
||
|
||
@api.depends('date_end')
|
||
def _compute_end_comparison(self):
|
||
for record in self:
|
||
now = date.today()
|
||
if record.date_end:
|
||
if record.date_end > now:
|
||
delta = record.date_end - now
|
||
if 60 <= delta.days < 90:
|
||
record.end_comparison = 'blue'
|
||
elif 30 <= delta.days < 60:
|
||
record.end_comparison = 'yellow'
|
||
elif delta.days < 30:
|
||
record.end_comparison = 'red'
|
||
else:
|
||
record.end_comparison = None
|
||
elif record.date_end < now:
|
||
record.end_comparison = 'red'
|
||
else:
|
||
record.end_comparison = None
|
||
else:
|
||
record.end_comparison = None
|
||
|
||
@api.depends('contract_id')
|
||
def _compute_date_end(self):
|
||
for record in self:
|
||
if record.contract_id:
|
||
if record.contract_id.contract_type_id.name == '正式合同(无固定)':
|
||
record.date_end = record.retire_date
|
||
else:
|
||
record.date_end = record.contract_id.date_end
|
||
else:
|
||
record.date_end = None
|
||
|
||
def renew(self):
|
||
employee_ids = self.env["hr.employee"].browse(
|
||
self._context.get("active_ids", self._context.get("active_id"))
|
||
)
|
||
for record in employee_ids:
|
||
if len(record) > 1:
|
||
raise UserError("只能选择一个员工续签")
|
||
return {
|
||
"name": "续签",
|
||
"type": "ir.actions.act_window",
|
||
"res_model": "yuthon.renewal.approval",
|
||
"view_mode": "form",
|
||
"context": {
|
||
"default_employee_id": self.id,
|
||
"default_current_status": self.contract_id.date_end,
|
||
"default_department1_id": self.department_id.id,
|
||
"default_contract_id": self.contract_id.id,
|
||
},
|
||
}
|
||
|
||
|
||
|
||
def update_social_base(self):
|
||
current_year = fields.Date.today().year
|
||
group_company = self.env['res.company'].search([('name', '=', '集团本部')], limit=1)
|
||
official_contract_type = self.env['hr.contract.type'].search(
|
||
[('name', '=', '正式合同(一签)')],
|
||
limit=1
|
||
)
|
||
eligible_employees = self.env['hr.contract'].search([
|
||
('employee_id.company_id', '=', group_company.id),
|
||
('contract_type_id', '=', official_contract_type.id),
|
||
('date_start', '>=', f'{current_year}-01-01'),
|
||
('date_start', '<=', f'{current_year}-12-31')
|
||
]).mapped('employee_id')
|
||
eighty_percent_items = [
|
||
'基本工资',
|
||
'职级工资',
|
||
'月绩效奖金'
|
||
]
|
||
full_amount_items = [
|
||
'通讯补贴',
|
||
'工龄工资'
|
||
]
|
||
for employee in eligible_employees:
|
||
if employee.contract_id.state == 'open':
|
||
payslip_id = self.env['hr.payslip'].create({
|
||
'employee_id': employee.id,
|
||
'name': f"{employee.name}的薪资单-{fields.Date.today().strftime('%Y%m')}",
|
||
})
|
||
payslip_id.compute_sheet()
|
||
eighty_lines = payslip_id.line_ids.filtered(
|
||
lambda l: l.name in eighty_percent_items
|
||
)
|
||
full_lines = payslip_id.line_ids.filtered(
|
||
lambda l: l.name in full_amount_items
|
||
)
|
||
eighty_sum = sum(eighty_lines.mapped('amount'))
|
||
total_eighty_part = math.ceil(eighty_sum * 0.8)
|
||
total_full_part = sum(full_lines.mapped('amount')) if full_lines else 0
|
||
final_total = total_eighty_part + total_full_part + 200
|
||
employee.write({
|
||
'last_year_salary': final_total,
|
||
'social_base_on': final_total,
|
||
'social_base_off': final_total
|
||
})
|
||
payslip_id.action_payslip_cancel()
|
||
payslip_id.unlink()
|
||
|
||
def get_my_employee_action(self):
|
||
current_user = self.env.user
|
||
my_employee = self.search([('user_id', '=', current_user.id)], limit=1)
|
||
return {
|
||
'type': 'ir.actions.act_window',
|
||
'name': '个人基础信息',
|
||
'res_model': 'hr.employee',
|
||
'res_id': my_employee.id,
|
||
'view_mode': 'form',
|
||
'view_type': 'form',
|
||
'target': 'current',
|
||
'view_id': self.env.ref('yuthon_hr_employee.view_basic_personal_information').id,
|
||
}
|
||
|
||
def change_password_wizard(self):
|
||
return {
|
||
'name': '修改密码',
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': 'reset.password.wizard',
|
||
'view_mode': 'form',
|
||
'target': 'new',
|
||
'context': {'default_employee_id': self.env.user.employee_ids.id
|
||
|
||
},
|
||
}
|
||
|
||
def compute_late_allowed(self):
|
||
employee = self.env["hr.employee"].search([('company_id', '=', 3)])
|
||
for employee_id in employee:
|
||
employee_id.late_allowed = 3
|
||
|
||
@api.onchange('user_role_id')
|
||
def onchange_user_role_id(self):
|
||
if self.user_role_id:
|
||
user_id = self.env['soong.user.role'].search([('id', '=', self.user_role_id.id)])
|
||
user_id.write({'personnel_ids': [(4, self.id.origin)]})
|
||
|
||
@api.depends('quota_issued', 'user_year')
|
||
def _compute_remainder_year(self):
|
||
for record in self:
|
||
if record.quota_issued > record.user_year:
|
||
record.remainder_year = record.quota_issued - record.user_year
|
||
else:
|
||
record.remainder_year = 0
|
||
|
||
@api.depends('user_role_ids', 'user_role_id', 'department_id', 'skills_certificates_ids')
|
||
def _compute_subsidy_amount(self):
|
||
for record in self:
|
||
amount = 0
|
||
quality_amount = 0
|
||
# 获取主角色名称
|
||
primary_role_name = record.user_role_id.name if record.user_role_id else ''
|
||
# 获取所有辅助角色名称列表
|
||
secondary_role_names = record.user_role_ids.mapped('name')
|
||
# 获取部门名称
|
||
department_name = record.department_id.name if record.department_id else ''
|
||
# 1. 财务部会计、出纳每月有100元补贴
|
||
if '会计岗' in (primary_role_name, *secondary_role_names) or '出纳' in (
|
||
primary_role_name, *secondary_role_names):
|
||
amount += 100
|
||
# 2. 司机分别有600元补贴(仅司机)
|
||
if '司机' in (primary_role_name, *secondary_role_names):
|
||
amount += 600
|
||
# 3. 运行班、高压班员工补贴200元/月
|
||
if department_name in ['运行班', '高压班']:
|
||
amount += 200
|
||
# 4. 宿舍管理长会有100元/月补贴
|
||
if '宿舍长' in (primary_role_name, *secondary_role_names):
|
||
amount += 100
|
||
# 5. 消防安全证的保安有50元/月补贴
|
||
has_fire_certificate = any(
|
||
cert.rank_name == '消防安全证'
|
||
for cert in record.skills_certificates_ids
|
||
)
|
||
if has_fire_certificate:
|
||
amount += 50
|
||
quality = any(
|
||
cert.rank_name == '质检证书'
|
||
for cert in record.skills_certificates_ids
|
||
)
|
||
if quality and department_name == '仓储组':
|
||
quality_amount += 100
|
||
|
||
if department_name == '领导班子':
|
||
record.security_amount = 0
|
||
elif '安全委员会' in (primary_role_name, *secondary_role_names):
|
||
record.security_amount = 320
|
||
else:
|
||
record.security_amount = 0
|
||
|
||
record.subsidy_amount = amount
|
||
record.quality_inspector_amount = quality_amount
|
||
|
||
@api.depends('converted', 'salary_year_jin_ids')
|
||
def _compute_total_coefficients(self):
|
||
for record in self:
|
||
company_employees = self.env['hr.employee'].search([])
|
||
record.total_coefficients = sum(employee.converted for employee in self.salary_year_jin_ids)
|
||
record.calculation_totals = sum(employee.conversion_factor for employee in company_employees)
|
||
|
||
@api.depends('contract_id')
|
||
def _compute_resource_calendar_id(self):
|
||
for employee in self:
|
||
employee.resource_calendar_id = employee.contract_id.resource_calendar_id.id
|
||
|
||
@api.depends_context('uid', 'company')
|
||
@api.depends('company_id')
|
||
def _compute_is_company(self):
|
||
active_company_ids = self.env.companies
|
||
for employee in self:
|
||
employee.is_company = employee.company_id in active_company_ids
|
||
|
||
def _search_part_of_company(self, operator, value):
|
||
if operator not in ('=', '!=') or not isinstance(value, bool):
|
||
raise UserError(('Operation not supported'))
|
||
company_ids = self.env.companies.ids # 获取公司ID列表
|
||
# 处理反向操作符
|
||
if not value:
|
||
operator = '!=' if operator == '=' else '='
|
||
# 使用company_id in company_ids的结构
|
||
if operator == '=':
|
||
return [('company_id', 'in', company_ids)]
|
||
else: # operator == '!='
|
||
return [('company_id', 'not in', company_ids)]
|
||
|
||
@api.depends('normal_overtime_hours', 'weekend_overtime_hours', 'holiday_overtime_hours')
|
||
def _compute_overtime_this_month(self):
|
||
for employee in self:
|
||
employee.overtime_this_month = employee.normal_overtime_hours + employee.weekend_overtime_hours + employee.holiday_overtime_hours
|
||
# 根据当前月份来选择具体的值
|
||
@api.depends('social_base_on', 'social_base_off')
|
||
def _compute_insurance_bases(self):
|
||
current_month = datetime.now().month
|
||
insurance_names = {
|
||
'基本医疗保险费': 'medical_insurance_base',
|
||
'基本养老保险费': 'pension_insurance_base',
|
||
'失业保险费': 'work_injury_insurance_base',
|
||
'住房公积金': 'housing_fund_base'
|
||
}
|
||
insurance_records = self.env['yuthon.payroll.insurances'].search([('name', 'in', list(insurance_names.keys()))])
|
||
for record in self:
|
||
# 初始化所有保险基数为0
|
||
for field_name in insurance_names.values():
|
||
setattr(record, field_name, 0)
|
||
base = record.social_base_on if 1 <= current_month <= 6 else record.social_base_off
|
||
for insurance in insurance_records:
|
||
field_name = insurance_names[insurance.name]
|
||
# 特殊处理:只有这两个人的住房公积金设为4000
|
||
if field_name == 'housing_fund_base' and record.name in ['郭爱娣', '黄炎方']:
|
||
record.housing_fund_base = 4000
|
||
continue # 跳过住房公积金的正常计算
|
||
|
||
# 正常计算其他保险和普通人的住房公积金
|
||
if base > insurance.employee_base_ceiling:
|
||
setattr(record, field_name, insurance.employee_base_ceiling)
|
||
elif base < insurance.employee_base_floor:
|
||
setattr(record, field_name, insurance.employee_base_floor)
|
||
else:
|
||
setattr(record, field_name, base)
|
||
|
||
# 根据薪资计算选择的月份 来获取确定的值
|
||
@api.depends('social_base_on', 'social_base_off', 'salary_month')
|
||
def _compute_insurance_salary(self):
|
||
insurance_names = {
|
||
'薪资医疗保险费': 'salary_medical_insurance',
|
||
'薪资养老保险费': 'salary_insurance',
|
||
'薪资保险费': 'salary_work_injury_insurance',
|
||
'薪资公积金': 'salary_housing_fund'
|
||
}
|
||
name_mapping = {
|
||
'基本医疗保险费': '薪资医疗保险费',
|
||
'基本养老保险费': '薪资养老保险费',
|
||
'失业保险费': '薪资保险费',
|
||
'住房公积金': '薪资公积金'
|
||
}
|
||
insurance_records = self.env['yuthon.payroll.insurances'].search([('name', 'in',['基本医疗保险费', '基本养老保险费', '失业保险费', '住房公积金'])])
|
||
for record in self:
|
||
for field_name in insurance_names.values():
|
||
setattr(record, field_name, 0)
|
||
base = record.social_base_on if 1 <= int(record.salary_month) <= 6 else record.social_base_off
|
||
for insurance in insurance_records:
|
||
insurance_name_key = name_mapping.get(insurance.name)
|
||
field_name = insurance_names.get(insurance_name_key)
|
||
if field_name == 'housing_fund_base' and record.name in ['郭爱娣', '黄炎方']:
|
||
record.housing_fund_base = 4000
|
||
continue
|
||
if base > insurance.employee_base_ceiling:
|
||
setattr(record, field_name, insurance.employee_base_ceiling)
|
||
elif base < insurance.employee_base_floor:
|
||
setattr(record, field_name, insurance.employee_base_floor)
|
||
else:
|
||
setattr(record, field_name, base)
|
||
|
||
@api.onchange('job_id')
|
||
def _onchange_job_id(self):
|
||
if self.job_id:
|
||
personnel_id = self.env['hr.job'].search([('job_personnel_ids', 'in', self.id.origin)])
|
||
job_id = self.env['hr.job'].search([('id', '=', self.job_id.id)])
|
||
if personnel_id:
|
||
# 如果找到,从 job_personnel_ids 中移除当前记录的人员
|
||
personnel_id.write({'job_personnel_ids': [(3, self.id.origin)]})
|
||
job_id.write({'job_personnel_ids': [(4, self.id.origin)]})
|
||
else:
|
||
job_id.write({'job_personnel_ids': [(4, self.id.origin)]})
|
||
|
||
@api.onchange('user_location')
|
||
def _onchange_user_location(self):
|
||
if self.user_location:
|
||
self.is_location = True
|
||
else:
|
||
self.is_location = False
|
||
|
||
@api.model_create_multi
|
||
def create(self, vals_list):
|
||
recs = super().create(vals_list)
|
||
for record in recs:
|
||
try:
|
||
if record.department_id:
|
||
record.department_id.do_department_to_res_groups()
|
||
if record.user_role_id:
|
||
record.user_role_id.do_role_to_res_groups()
|
||
except Exception as e:
|
||
pass
|
||
return recs
|
||
|
||
# def write(self, values):
|
||
# for record in self:
|
||
# try:
|
||
# if "department_id" in values:
|
||
# record.do_group_department_id(values.get("department_id"))
|
||
# if "user_role_id" in values:
|
||
# record.do_group_user_role_id(values.get("user_role_id"))
|
||
# except Exception as e:
|
||
# pass
|
||
# return super().write(values)
|
||
|
||
def do_group_department_id(self, department_id):
|
||
user_ids = self.user_id.ids
|
||
# 移除原来的数据
|
||
old_res_groups_objs = self.sudo().env["res.groups"].search([("department_ids", "in", self.department_id.ids)])
|
||
if old_res_groups_objs:
|
||
for res_groups_obj in old_res_groups_objs:
|
||
res_groups_obj.sudo().write({
|
||
"users": [(3, id) for id in user_ids]
|
||
})
|
||
# 增加新的元素
|
||
department_obj = self.sudo().env["hr.department"].search([("id", "=", department_id)], limit=1)
|
||
res_groups_objs = self.sudo().env["res.groups"].search([("department_ids", "in", department_obj.ids)])
|
||
if res_groups_objs:
|
||
for res_groups_obj in res_groups_objs:
|
||
res_groups_obj.sudo().write({
|
||
"users": [(4, id) for id in user_ids]
|
||
})
|
||
|
||
# def do_group_user_role_id(self, user_role_id):
|
||
# # 移除原来的数据
|
||
# user_ids = self.user_id.ids
|
||
# old_res_groups_objs = self.sudo().env["res.groups"].search([("user_role_ids", "in", self.user_role_id.ids)])
|
||
# if old_res_groups_objs:
|
||
# for res_groups_obj in old_res_groups_objs:
|
||
# res_groups_obj.sudo().write({"users": [(3, id) for id in user_ids]})
|
||
# # 增加新的元素
|
||
# role_obj = self.sudo().env["soong.user.role"].search([("id", "=", user_role_id)], limit=1)
|
||
# res_groups_objs = self.sudo().env["res.groups"].search([("user_role_ids", "in", role_obj.ids)])
|
||
# if res_groups_objs:
|
||
# for res_groups_obj in res_groups_objs:
|
||
# res_groups_obj.sudo().write({
|
||
# "users": [(4, id) for id in user_ids]
|
||
# })
|
||
|
||
@api.onchange('number')
|
||
def onchange_number(self):
|
||
for num in self:
|
||
if num.department_id.department_code:
|
||
num.number = num.department_id.department_code + num.number
|
||
else:
|
||
num.number = ''
|
||
|
||
def action_batch_fix_resource_calendar(self):
|
||
"""根据所属公司批量修复员工工作时间"""
|
||
company_calendar_map = {
|
||
3: '行政考勤-物管',
|
||
'集团本部': '行政考勤-本部',
|
||
'天河粮储': '行政考勤-粮储',
|
||
}
|
||
updated_count = 0
|
||
skipped_count = 0
|
||
failed_employees = []
|
||
|
||
# 预加载所有需要的 resource.calendar
|
||
calendar_names = list(company_calendar_map.values())
|
||
calendars = self.env['resource.calendar'].search([('name', 'in', calendar_names)])
|
||
calendar_map = {cal.name: cal for cal in calendars}
|
||
|
||
for employee in self:
|
||
company_name = employee.company_id.name if employee.company_id else False
|
||
if not employee.company_id or (employee.company_id.id not in company_calendar_map and company_name not in company_calendar_map):
|
||
skipped_count += 1
|
||
continue
|
||
|
||
calendar_name = company_calendar_map.get(company_name) or company_calendar_map.get(employee.company_id.id)
|
||
calendar = calendar_map.get(calendar_name)
|
||
|
||
if not calendar:
|
||
skipped_count += 1
|
||
continue
|
||
|
||
if not employee.contract_id:
|
||
skipped_count += 1
|
||
continue
|
||
|
||
try:
|
||
employee.contract_id.write({'resource_calendar_id': calendar.id})
|
||
updated_count += 1
|
||
except Exception as e:
|
||
failed_employees.append('%s(%s)' % (employee.name, employee.id))
|
||
continue
|
||
|
||
parts = ['成功更新 %d 条记录,跳过 %d 条。' % (updated_count, skipped_count)]
|
||
if failed_employees:
|
||
parts.append('失败 %d 条:%s' % (len(failed_employees), ', '.join(failed_employees[:5])))
|
||
if len(failed_employees) > 5:
|
||
parts.append('...等共 %d 条' % len(failed_employees))
|
||
message = '\n'.join(parts)
|
||
|
||
return {
|
||
'type': 'ir.actions.client',
|
||
'tag': 'display_notification',
|
||
'params': {
|
||
'title': '批量修复工作时间完成',
|
||
'message': message,
|
||
'type': 'success',
|
||
'sticky': False,
|
||
}
|
||
}
|
||
|
||
def action_hr_employee(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "批量更新员工",
|
||
"res_model": "yuthon.hr.employee.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
}
|
||
|
||
def action_hr_time_form(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "批量更新时间表",
|
||
"res_model": "yuthon.hr.employee.time.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
}
|
||
|
||
def action_hr_state(self):
|
||
employee = self.env["hr.employee"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
for employee_id in employee:
|
||
employee_id.onchange_user_role_id()
|
||
|
||
@api.depends('confirmation_date')
|
||
def _compute_regularization_year(self):
|
||
for record in self:
|
||
if record.confirmation_date:
|
||
record.regularization_year = fields.Date.today().year - record.confirmation_date.year
|
||
else:
|
||
record.regularization_year = 0
|
||
|
||
@api.depends('entry_date', 'salary_date')
|
||
def _compute_salary_year(self):
|
||
"""基于薪资计算时间的司龄年份计算"""
|
||
for record in self:
|
||
if record.entry_date and record.salary_date:
|
||
# 获取薪资计算时间和入职日期
|
||
salary_date = record.salary_date
|
||
entry_date = record.entry_date
|
||
years_diff = salary_date.year - entry_date.year
|
||
# 检查月份和日期,如果薪资计算时间的月份和日期早于入职日期,则减去1年
|
||
if (salary_date.month, salary_date.day) < (entry_date.month, entry_date.day):
|
||
years_diff -= 1
|
||
record.salary_year = max(0, years_diff)
|
||
else:
|
||
record.salary_year = 0
|
||
|
||
@api.depends('entry_date')
|
||
def _compute_company_old(self):
|
||
"""司龄计算"""
|
||
for record in self:
|
||
if record.entry_date:
|
||
today = datetime.now().date()
|
||
record.company_old_year = today.year - record.entry_date.year + 1
|
||
# 原有的完整年月计算保持不变
|
||
years_diff = today.year - record.entry_date.year
|
||
months_diff = today.month - record.entry_date.month
|
||
if today.month < record.entry_date.month:
|
||
years_diff -= 1
|
||
months_diff += 12
|
||
record.company_old = str(years_diff) + '年' + str(months_diff) + '月'
|
||
else:
|
||
record.company_old = None
|
||
record.company_old_year = 0
|
||
|
||
@api.depends('open_start_time', 'interrupt_work_month')
|
||
def _compute_social_insurance_info(self):
|
||
for record in self:
|
||
cumulative_months = 0
|
||
if record.open_start_time:
|
||
today = datetime.now().date()
|
||
delta = relativedelta(today, record.open_start_time)
|
||
cumulative_months = delta.years * 12 + delta.months
|
||
record.cumulative_work_month = cumulative_months
|
||
total_months = cumulative_months - record.interrupt_work_month
|
||
record.compute_month = total_months
|
||
if total_months <= 0:
|
||
social_insurance_time_str = "0个月"
|
||
else:
|
||
years = total_months // 12
|
||
months = total_months % 12
|
||
social_insurance_time_str = f"{years}年{months}个月" if years > 0 and months > 0 else (
|
||
f"{years}年" if years > 0 else f"{months}个月")
|
||
record.social_insurance_time = social_insurance_time_str
|
||
record.compute_char = social_insurance_time_str
|
||
|
||
@api.depends('cumulative_work_month')
|
||
def _compute_cumulative_work_years(self):
|
||
for record in self:
|
||
if record.cumulative_work_month:
|
||
if record.cumulative_work_month <= 0:
|
||
record.social_insurance_time = "0个月"
|
||
years = record.cumulative_work_month // 12
|
||
months = record.cumulative_work_month % 12
|
||
if years > 0:
|
||
result_str = f"{years}年"
|
||
if months > 0:
|
||
result_str += f"{months}个月"
|
||
else:
|
||
result_str = f"{months}个月"
|
||
record.cumulative_work_years = result_str
|
||
else:
|
||
record.cumulative_work_years = None
|
||
|
||
@api.depends('identification_id') # 只依赖身份证号码
|
||
def _compute_info(self):
|
||
for record in self:
|
||
if record.identification_id:
|
||
self.is_identification = True
|
||
id_number = record.identification_id.upper()
|
||
if len(id_number) != 18:
|
||
record._clear_info()
|
||
# raise UserError("身份证号码长度应为18位")
|
||
# try:
|
||
if not (all(c.isdigit() for c in id_number[:-1]) and (
|
||
id_number[-1].isdigit() or id_number[-1] == 'X')):
|
||
raise ValueError("身份证号码包含无效字符")
|
||
|
||
year = int(id_number[6:10])
|
||
month = int(id_number[10:12])
|
||
day = int(id_number[12:14])
|
||
record.birthday = date(year, month, day)
|
||
record.birthday_month = month
|
||
|
||
today = date.today()
|
||
if year is not None:
|
||
record.old = today.year - year - ((today.month, today.day) < (month, day))
|
||
else:
|
||
record.old = 0
|
||
gender_code = int(id_number[-2])
|
||
record.user_sex = 'girl' if gender_code % 2 == 0 else 'boy'
|
||
|
||
# except ValueError as e:
|
||
# record._clear_info()
|
||
# raise UserError(str(e) or "身份证号码格式不正确")
|
||
else:
|
||
record._clear_info()
|
||
self.is_identification = False
|
||
|
||
def _clear_info(self):
|
||
self.birthday = False
|
||
self.birthday_month = False
|
||
self.old = 0
|
||
self.user_sex = False
|
||
|
||
@api.depends('retire_date')
|
||
def _compute_retire_years(self):
|
||
"""离退休年限计算"""
|
||
for record in self:
|
||
if record.retire_date:
|
||
today = datetime.now().date()
|
||
years_diff = today.year - record.retire_date.year
|
||
compute = years_diff
|
||
if compute < 0:
|
||
record.retire_years = -1 * compute
|
||
else:
|
||
record.retire_years = 0
|
||
else:
|
||
record.retire_years = 0
|
||
|
||
@api.depends('normal_overtime_hours', 'weekend_overtime_hours')
|
||
def _compute_all_overtime_hours(self):
|
||
for record in self:
|
||
record.all_overtime_hours = record.normal_overtime_hours + record.weekend_overtime_hours
|
||
|
||
def hr_employee_time(self):
|
||
for record in self:
|
||
record.duty_replacement_card = 0
|
||
|
||
@api.depends('name')
|
||
def _compute_pinyin(self):
|
||
for employee in self:
|
||
employee.pinyin_name = ''.join(lazy_pinyin(employee.name)) if employee.name else ''
|
||
employee.pinyin_min = ''.join(lazy_pinyin(employee.name, style=STYLE_FIRST_LETTER)) if employee.name else ''
|
||
|
||
@api.model
|
||
def _name_search(self, name, domain=None, operator='ilike', limit=None, order=None):
|
||
domain = domain or []
|
||
if name:
|
||
has_chinese = any('\u4e00' <= char <= '\u9fff' for char in name)
|
||
if has_chinese:
|
||
# 包含中文:使用模糊匹配
|
||
search_domain = [('name', operator, name)]
|
||
else:
|
||
# 纯字母/数字:使用精确匹配
|
||
search_domain = ['|',
|
||
('pinyin_name', operator, name),
|
||
('name', '=', name)]
|
||
domain = expression.AND([search_domain, domain])
|
||
return self._search(domain, limit=limit, order=order)
|
||
|
||
|
||
def employee_log(self):
|
||
vacation_id = self.env['yuthon.vacation.reports'].search([('employee_id', '=', self.id)])
|
||
if vacation_id:
|
||
return {
|
||
'name': '休假记录',
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': 'yuthon.vacation.reports',
|
||
'view_mode': 'form',
|
||
'res_id': vacation_id.id,
|
||
'target': 'new'
|
||
}
|
||
|
||
def update_parental_leave(self):
|
||
today = datetime.now().date()
|
||
# 如果 self 有具体员工(个人调用),只查该员工的合同;否则(定时任务)查全部
|
||
if self:
|
||
active_contracts = self.env['hr.contract'].search([
|
||
('state', '=', 'open'),
|
||
('contract_type_id.name', 'ilike', '正式合同'),
|
||
('employee_id', 'in', self.ids)
|
||
])
|
||
else:
|
||
active_contracts = self.env['hr.contract'].search([
|
||
('state', '=', 'open'),
|
||
('contract_type_id.name', 'ilike', '正式合同'),
|
||
('employee_id', '!=', False)
|
||
])
|
||
allocate_config = self.env['yuthon.parenting.allocate'].search([], limit=1)
|
||
valid_limit = allocate_config.valid_limit # 截至年龄(如3)
|
||
cycle_years = allocate_config.cycle_years # 周期年数(如1)
|
||
distribute = allocate_config.distribute #
|
||
if not active_contracts:
|
||
return
|
||
employees = active_contracts.mapped('employee_id')
|
||
for employee in employees:
|
||
child_contacts = employee.emergency_contact_ids.filtered(
|
||
lambda line: line.user_relationship in ['boys', 'daughter']
|
||
and line.user_sfz
|
||
)
|
||
if not child_contacts:
|
||
continue
|
||
|
||
# 解析子女出生日期,判断是否符合条件(≤3岁)
|
||
for contact in child_contacts:
|
||
sfz = contact.user_sfz.strip()
|
||
if not sfz:
|
||
continue
|
||
try:
|
||
if len(sfz) == 18:
|
||
birth_str = sfz[6:14]
|
||
birth_date = datetime.strptime(birth_str, '%Y%m%d').date()
|
||
else:
|
||
continue
|
||
except (ValueError, IndexError):
|
||
continue
|
||
|
||
valid_end_date = birth_date + relativedelta(years=valid_limit)
|
||
if today > valid_end_date:
|
||
continue
|
||
cycle_list = []
|
||
current_cycle_start = birth_date
|
||
total_cycles = int(valid_limit / cycle_years)
|
||
|
||
for i in range(1, total_cycles + 1):
|
||
current_cycle_end = current_cycle_start + relativedelta(years=cycle_years)
|
||
cycle_list.append({
|
||
'cycle_num': i,
|
||
'start': current_cycle_start,
|
||
'end': current_cycle_end
|
||
})
|
||
current_cycle_start = current_cycle_end
|
||
|
||
date_from = birth_date
|
||
date_to = valid_end_date - timedelta(days=1)
|
||
for cycle in cycle_list:
|
||
if today <= cycle['end']:
|
||
date_from = cycle['start']
|
||
date_to = cycle['end'] - timedelta(days=1)
|
||
break
|
||
|
||
total_days = distribute
|
||
if total_days <= 0:
|
||
continue
|
||
|
||
holiday_status_id = self.env['hr.leave.type'].search([('name', '=', '育儿假')], limit=1)
|
||
|
||
# 检查是否已存在相同分配记录,避免重复
|
||
existing_allocation = self.env['hr.leave.allocation'].search([
|
||
('employee_id', '=', employee.id),
|
||
('name', '=', '育儿假分配'),
|
||
('allocation_type', '=', 'regular'),
|
||
('date_from', '=', date_from),
|
||
('date_to', '=', date_to),
|
||
], limit=1)
|
||
if not existing_allocation:
|
||
self.env['hr.leave.allocation'].create({
|
||
'name': '育儿假分配',
|
||
'allocation_type': 'regular',
|
||
'employee_id': employee.id,
|
||
'date_from': date_from,
|
||
'date_to': date_to,
|
||
'holiday_status_id': holiday_status_id.id,
|
||
'number_of_days': total_days,
|
||
})
|
||
|
||
reports_id = self.env['yuthon.vacation.reports'].search([
|
||
('employee_id', '=', employee.id),
|
||
('vacation_date', '=', fields.Date.today().replace(day=1))
|
||
], limit=1) #
|
||
if reports_id:
|
||
existing_report_line = reports_id.parenting_line_ids.search([
|
||
('vacation_id', '=', reports_id.id),
|
||
('employee_id', '=', employee.id),
|
||
('update_date', '=', date_from),
|
||
('end_date', '=', date_to),
|
||
('text','in', ['育儿假分配', '上月额度继承'])
|
||
], limit=1)
|
||
if not existing_report_line:
|
||
reports_id.parenting_line_ids.sudo().create({
|
||
'vacation_id': reports_id.id,
|
||
'employee_id': employee.id,
|
||
'update_date': date_from,
|
||
'lines': total_days,
|
||
'end_date': date_to,
|
||
'text': '育儿假分配'
|
||
})
|
||
|
||
|
||
def regularization_task(self):
|
||
today = fields.Date.today()
|
||
target_date = today + timedelta(days=15)
|
||
eligible_employees = self.search([
|
||
('is_create', '=', False),
|
||
('contract_id.is_probation', '=', True),
|
||
('contract_id.end_date', '=', target_date),
|
||
('contract_id', '!=', False)
|
||
])
|
||
if not eligible_employees:
|
||
return
|
||
|
||
for employee in eligible_employees:
|
||
contract_structure_type = employee.contract_id.structure_type_id
|
||
approval_name = f"转正申请表-{employee.name}"
|
||
self.env['yuthon.regularization.approval'].create({
|
||
'name': approval_name,
|
||
'users_id': employee.user_id.id,
|
||
'create_uid': employee.user_id.id,
|
||
'structure_type_id': contract_structure_type.id,
|
||
'structure_type_id1': contract_structure_type.id,
|
||
'is_adjust': False,
|
||
})
|
||
menu_id = self.env['ir.ui.menu'].search([('name', '=', '转正申请表')])
|
||
url = f'https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=yuthon.regularization.approval&view_type=list&menu_id={menu_id.id}'
|
||
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa',
|
||
user_id=employee.wecom_userid,
|
||
title="转正申请",
|
||
description=f"""您好!近期即将开展您的转正流程,现就转正申请事宜温馨提醒如下,确保流程顺利推进:
|
||
待办工作流:已为您生成「转正申请审批单」待办任务,可登录 OA 查看处理;
|
||
提交时限:请于试用期截止前 1 周(即{employee.contract_id.end_date.year}年{employee.contract_id.end_date.month}月{employee.contract_id.end_date.day}日)完成审批单提交,逾期可能影响转正进度;
|
||
材料准备:需同步上传《转正申请书》(可向人事部门索取,企业微信联系人事对接人)。""",
|
||
url=url, btntxt="详细信息")
|
||
|
||
employee.write({'is_create': True})
|
||
|
||
def _get_notify_users(self, task_config):
|
||
"""从 task_config 获取需要通知的用户列表.
|
||
"""
|
||
user_ids = set(task_config.user_ids.ids)
|
||
role_ids = set(task_config.users_role_group_ids.mapped('user_role_ids').ids)
|
||
if task_config.users_role_id:
|
||
role_ids.add(task_config.users_role_id.id)
|
||
if role_ids:
|
||
all_target_roles = self.env['soong.user.role'].browse(list(role_ids))
|
||
role_employees = self.env['hr.employee'].search([
|
||
'|',
|
||
('user_role_id', 'in', all_target_roles.ids),
|
||
('user_role_ids', 'in', all_target_roles.ids),
|
||
('user_id', '!=', False),
|
||
])
|
||
for emp in role_employees:
|
||
user_ids.add(emp.user_id.id)
|
||
return self.env['res.users'].browse(list(user_ids))
|
||
|
||
def task_retire_reminder(self):
|
||
today = fields.Date.today()
|
||
target_date = today + timedelta(days=30)
|
||
employees = self.search([
|
||
('retire_date', '=', target_date),
|
||
('user_state', 'in', ['in', 'borrow', 'expatriate', 'adjunct']),
|
||
])
|
||
if not employees:
|
||
return
|
||
task_config = self.env['yuthon.hr.task'].search([('cron_id.name', '=', '定时任务:退休提醒')], limit=1)
|
||
if not task_config:
|
||
return
|
||
notify_users = self._get_notify_users(task_config)
|
||
if not notify_users:
|
||
return
|
||
count = len(employees)
|
||
for user in notify_users:
|
||
hr_employee = user.employee_ids.filtered(lambda e: e.wecom_userid)[:1]
|
||
if not hr_employee:
|
||
continue
|
||
result_data = self.sudo().env['wecom.apps'].sync_send_message_textcard(
|
||
category='oa',
|
||
user_id=hr_employee.wecom_userid,
|
||
title='退休提醒',
|
||
description=f"提醒:有{count}个人即将退休,请移至工作台查看。",
|
||
url='https://oa.thtzjt.com/web?debug=1#action=3584&cids=1&menu_id=9668',
|
||
btntxt='详细信息',
|
||
)
|
||
confirm = self.env['yuthon.task.confirm'].create({
|
||
'task_id': task_config.id,
|
||
'user_id': user.id,
|
||
'state': 'read',
|
||
})
|
||
if result_data and len(result_data) > 1:
|
||
msg_id = result_data[1].get('msgid')
|
||
if msg_id:
|
||
confirm.sudo().write({'msg_id': msg_id})
|
||
|
||
def task_separation_reminder(self):
|
||
today = fields.Date.today()
|
||
target_date = today + timedelta(days=30)
|
||
workflow_records = self.env['soong.workflow.record'].search([
|
||
('workflow_id.name', '=', '离职申请'),
|
||
('approve_state', '=', 'end'),
|
||
('reclaim_state', '=', False),
|
||
])
|
||
leaving_ids = []
|
||
for record in workflow_records:
|
||
try:
|
||
leaving = self.env[record.res_model].browse(record.res_id)
|
||
if leaving and leaving.separation_date == target_date and not leaving.is_execution:
|
||
leaving_ids.append(leaving)
|
||
except Exception:
|
||
continue
|
||
if not leaving_ids:
|
||
return
|
||
task_config = self.env['yuthon.hr.task'].search([('cron_id.name', '=', '定时任务:离职提醒')], limit=1)
|
||
if not task_config:
|
||
return
|
||
notify_users = self._get_notify_users(task_config)
|
||
if not notify_users:
|
||
return
|
||
count = len(leaving_ids)
|
||
for user in notify_users:
|
||
hr_employee = user.employee_ids.filtered(lambda e: e.wecom_userid)[:1]
|
||
if not hr_employee:
|
||
continue
|
||
result_data = self.sudo().env['wecom.apps'].sync_send_message_textcard(
|
||
category='oa',
|
||
user_id=hr_employee.wecom_userid,
|
||
title='离职提醒',
|
||
description=f"提醒:有{count}个人即将离职,请移至工作台查看。",
|
||
url='https://oa.thtzjt.com/web?debug=1#action=3584&cids=1&menu_id=9668',
|
||
btntxt='详细信息',
|
||
)
|
||
confirm = self.env['yuthon.task.confirm'].create({
|
||
'task_id': task_config.id,
|
||
'user_id': user.id,
|
||
'state': 'read',
|
||
})
|
||
if result_data and len(result_data) > 1:
|
||
msg_id = result_data[1].get('msgid')
|
||
if msg_id:
|
||
confirm.sudo().write({'msg_id': msg_id})
|