# -*- 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})