Files
2026-09-02 12:01:32 +08:00

1411 lines
68 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 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})