342 lines
15 KiB
Python
342 lines
15 KiB
Python
from odoo import api, fields, models, _
|
|
import re
|
|
from pypinyin import pinyin, Style
|
|
from datetime import date, datetime
|
|
|
|
|
|
class HrPayrollStructure(models.Model):
|
|
_inherit = 'hr.payroll.structure'
|
|
_order = 'sequence asc'
|
|
|
|
company_id = fields.Many2one('res.company', string="公司", default=lambda self: self.env.company)
|
|
yuthon_company = fields.Selection([('thtz', 'thtz')], default="thtz")
|
|
type_id = fields.Many2one('hr.payroll.structure.type', string="职级类型")
|
|
level_id = fields.Many2one('yuthon.payroll.type.setting', string="职级类别")
|
|
lv_name = fields.Char(related='level_id.name', string='级别名称', store=True)
|
|
name = fields.Char(string="职级名称")
|
|
sequence = fields.Char(string='级别序号', store=True)
|
|
create_time = fields.Date(string="生效日期", default=fields.Date.today())
|
|
end_time = fields.Date(string="失效日期")
|
|
is_enable = fields.Boolean(string="启用", compute= "_compute_is_enable", store=True)
|
|
currency_id = fields.Many2one("res.currency", string="货币", required=True, default=lambda self: self.env.company.currency_id)
|
|
base_salary = fields.Monetary(string="基本工资", currency_field="currency_id")
|
|
rank_salary = fields.Monetary(string="职级工资", currency_field="currency_id")
|
|
communication_subsidy = fields.Monetary(string="通讯补贴", currency_field="currency_id")
|
|
position_salary = fields.Monetary(string="岗位工资", currency_field="currency_id")
|
|
position_subsidy = fields.Monetary(string="岗位补贴", currency_field="currency_id")
|
|
integrated_subsidy = fields.Monetary(string="综合补贴", currency_field="currency_id")
|
|
advance_performance_bonuses = fields.Monetary(string="月绩效奖金", currency_field="currency_id")
|
|
renk_log_ids = fields.One2many("rank.log.line", "structure_id", string="职级日志明细行")
|
|
is_company = fields.Boolean(string="本公司", default=True, compute='_compute_is_company',
|
|
search='_search_part_of_company')
|
|
|
|
@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):
|
|
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('create_time', 'end_time')
|
|
def _compute_is_enable(self):
|
|
today = fields.Date.today()
|
|
for record in self:
|
|
if not record.create_time:
|
|
record.is_enable = False
|
|
elif not record.end_time:
|
|
record.is_enable = record.create_time <= today
|
|
else:
|
|
record.is_enable = record.create_time <= today <= record.end_time
|
|
|
|
@api.model
|
|
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
|
|
res = super(HrPayrollStructure, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby,
|
|
lazy=lazy)
|
|
if 'lv' in groupby:
|
|
def sort_by_sequence_number(item):
|
|
lv = item.get('lv')
|
|
if lv:
|
|
rec = self.search([('lv', '=', lv)], limit=1)
|
|
if rec and rec.sequence:
|
|
match = re.findall(r'\d+', rec.sequence)
|
|
if match:
|
|
number_str = "".join(match)
|
|
try:
|
|
number = int(number_str)
|
|
return (not number_str.startswith('0'), number)
|
|
except ValueError:
|
|
return (False, 0)
|
|
return (False, 0) # 首位非0的放前面
|
|
res.sort(key=sort_by_sequence_number)
|
|
return res
|
|
|
|
def _handle_salary_rule(self, field_name, amount):
|
|
category_id = self.env['hr.salary.rule.category'].search([('name', '=', '职级')], limit=1)
|
|
if amount:
|
|
name_pinyin = pinyin(field_name, style=Style.FIRST_LETTER)
|
|
code = "".join([p[0][0] for p in name_pinyin]).upper()
|
|
struct_id = self.id.origin if hasattr(self.id, 'origin') else self.id
|
|
salary_id = self.env['hr.salary.rule'].search(
|
|
[('name', '=', field_name), ('struct_id', '=', struct_id)], limit=1)
|
|
if not salary_id:
|
|
self.env['hr.salary.rule'].create({
|
|
'name': field_name,
|
|
'category_id': category_id.id,
|
|
'code': code,
|
|
'amount_fix': amount,
|
|
'struct_id': struct_id,
|
|
})
|
|
else:
|
|
salary_id.write({
|
|
'amount_fix': amount
|
|
})
|
|
|
|
@api.onchange('base_salary')
|
|
def _onchange_base_salary(self):
|
|
self._handle_salary_rule('基本工资', self.base_salary)
|
|
|
|
@api.onchange('rank_salary')
|
|
def _onchange_rank_salary(self):
|
|
self._handle_salary_rule('职级工资', self.rank_salary)
|
|
|
|
@api.onchange('communication_subsidy')
|
|
def _onchange_communication_subsidy(self):
|
|
self._handle_salary_rule('通讯补贴', self.communication_subsidy)
|
|
|
|
@api.onchange('position_salary')
|
|
def _onchange_position_salary(self):
|
|
self._handle_salary_rule('岗位工资', self.position_salary)
|
|
|
|
@api.onchange('position_subsidy')
|
|
def _onchange_position_subsidy(self):
|
|
self._handle_salary_rule('岗位补贴', self.position_subsidy)
|
|
|
|
@api.onchange('integrated_subsidy')
|
|
def _onchange_integrated_subsidy(self):
|
|
self._handle_salary_rule('综合补贴', self.integrated_subsidy)
|
|
|
|
@api.onchange('advance_performance_bonuses')
|
|
def _onchange_advance_performance_bonuses(self):
|
|
self._handle_salary_rule('月绩效奖金', self.advance_performance_bonuses)
|
|
|
|
def open_record(self):
|
|
return {
|
|
"type": "ir.actions.act_window",
|
|
"res_model": 'hr.payroll.structure',
|
|
"res_id": self.id,
|
|
"name": "职级详情",
|
|
"view_mode": "form",
|
|
}
|
|
|
|
def write(self, vals):
|
|
result = super(HrPayrollStructure, self).write(vals)
|
|
if result:
|
|
field_mapping = {
|
|
'base_salary': '基本工资',
|
|
'rank_salary': '职级工资',
|
|
'communication_subsidy': '通讯补贴',
|
|
'position_salary': '岗位工资',
|
|
'advance_performance_bonuses': '月绩效奖金' # 补充新增字段
|
|
}
|
|
for record in self:
|
|
for field, name in field_mapping.items():
|
|
if field in vals:
|
|
record._handle_salary_rule(name, record[field])
|
|
return result
|
|
|
|
def action_yuthon_payroll_wizard(self):
|
|
return {
|
|
"type": "ir.actions.act_window",
|
|
"name": "导入薪资",
|
|
"res_model": "yuthon.payroll.wizard",
|
|
"view_mode": "form",
|
|
"target": "new",
|
|
}
|
|
|
|
def generate_salary_rules(self, company_code, field_mapping):
|
|
# 搜索符合条件的薪资结构
|
|
company_id = self.env['res.company'].search([('name', '=', company_code)], limit=1)
|
|
structure_ids = self.env['hr.payroll.structure'].search([('company_id', '=', company_id.id)])
|
|
for structure in structure_ids:
|
|
# # 清空当前结构的规则
|
|
# structure.rule_ids.unlink()
|
|
# 初始化规则列表
|
|
rule_list = []
|
|
for rule_name, field_attr in field_mapping.items():
|
|
field_amount = getattr(structure, field_attr)
|
|
if field_amount is not None:
|
|
name_pinyin = pinyin(rule_name, style=Style.FIRST_LETTER)
|
|
code = "".join([p[0][0] for p in name_pinyin]).upper()
|
|
category_id = self.env['hr.salary.rule.category'].search([('name', '=', '职级')], limit=1)
|
|
rule_list.append([0, 0, {
|
|
'name': rule_name,
|
|
'category_id': category_id.id,
|
|
'code': code,
|
|
'amount_fix': field_amount,
|
|
'struct_id': structure.id,
|
|
'company_id': structure.company_id.id
|
|
}])
|
|
if rule_list:
|
|
structure.write({'rule_ids': rule_list})
|
|
|
|
def salary_rule_data(self):
|
|
# 定义不同公司的字段映射
|
|
company_mappings = {
|
|
# '天河物管': {
|
|
# '基本工资': 'base_salary',
|
|
# '岗位工资': 'position_salary',
|
|
# '综合补贴': 'integrated_subsidy',
|
|
# '通讯补贴': 'communication_subsidy'
|
|
# },
|
|
'天河粮储': {
|
|
'基本工资': 'base_salary',
|
|
'职级工资': 'rank_salary',
|
|
'月绩效奖金': 'advance_performance_bonuses'
|
|
}
|
|
}
|
|
|
|
for company_code, field_mapping in company_mappings.items():
|
|
self.generate_salary_rules(company_code, field_mapping)
|
|
|
|
def action_select_delete_wizard(self):
|
|
return {
|
|
"type": "ir.actions.act_window",
|
|
"name": "选择删除",
|
|
"res_model": "select.delete.wizard",
|
|
"view_mode": "form",
|
|
"target": "new",
|
|
}
|
|
|
|
def delete_structure(self):
|
|
payroll_ids = self.env["hr.payroll.structure"].browse(
|
|
self._context.get('active_ids', self._context.get('active_id')))
|
|
for payroll in payroll_ids:
|
|
for rule in payroll.rule_ids:
|
|
rule.sudo().unlink()
|
|
|
|
|
|
def update_payroll_items(self):
|
|
payroll_ids = self.env["hr.payroll.structure"].browse(
|
|
self._context.get('active_ids', self._context.get('active_id')))
|
|
category_id = self.env["hr.salary.rule.category"].search([('name', '=', '常用项')])
|
|
for payroll in payroll_ids:
|
|
for rule in payroll.rule_ids:
|
|
if rule.name == '工会会费':
|
|
rule.write({'category_id': category_id.id})
|
|
|
|
def rank_log_wizard(self):
|
|
return {
|
|
"type": "ir.actions.act_window",
|
|
"name": "职级日志",
|
|
"res_model": "rank.log.wizard",
|
|
"view_mode": "form",
|
|
"target": "new",
|
|
}
|
|
|
|
def update_rank(self):
|
|
payroll_ids = self.env["hr.payroll.structure"].browse(
|
|
self._context.get('active_ids', self._context.get('active_id')))
|
|
for payroll in payroll_ids:
|
|
log_data = {
|
|
'structure_id': payroll.id,
|
|
'base_salary': payroll.base_salary,
|
|
'rank_salary': payroll.rank_salary,
|
|
'communication_subsidy': payroll.communication_subsidy,
|
|
'position_salary': payroll.position_salary,
|
|
'advance_performance_bonuses': payroll.advance_performance_bonuses,
|
|
'effective_date': payroll.create_time,
|
|
'lapse_date': payroll.end_time,
|
|
'is_enable': payroll.is_enable
|
|
}
|
|
self.env['rank.log.line'].create(log_data)
|
|
|
|
def empty_the_log(self):
|
|
payroll_ids = self.env["hr.payroll.structure"].browse(
|
|
self._context.get('active_ids', self._context.get('active_id')))
|
|
for payroll in payroll_ids:
|
|
payroll.renk_log_ids.unlink()
|
|
|
|
def onetime(self):
|
|
dec_2025_start = datetime(2025, 10, 31).date()
|
|
payroll_ids = self.env["hr.payroll.structure"].browse(
|
|
self._context.get('active_ids', self._context.get('active_id')))
|
|
for payroll in payroll_ids:
|
|
payroll.end_time = dec_2025_start
|
|
payroll.is_enable = False
|
|
|
|
def two_time(self):
|
|
dec_2025_start = datetime(2025, 11, 1).date()
|
|
payroll_ids = self.env["hr.payroll.structure"].browse(
|
|
self._context.get('active_ids', self._context.get('active_id')))
|
|
for payroll in payroll_ids:
|
|
payroll.create_time = dec_2025_start
|
|
payroll.is_enable = True
|
|
|
|
def rank_log_time(self):
|
|
"""定时任务:根据日志记录的生效时间更新主模型字段值"""
|
|
# 出发一下
|
|
today = date.today()
|
|
for structure in self.env['hr.payroll.structure'].search([('company_id.name', '=', '集团本部')]):
|
|
# 获取该职级下所有有效的日志记录
|
|
valid_logs = structure.renk_log_ids.filtered(
|
|
lambda log: log.is_enable and log.effective_date and log.effective_date <= today and (not log.lapse_date or log.lapse_date >= today)
|
|
).sorted(key=lambda l: l.effective_date, reverse=True)
|
|
if valid_logs:
|
|
latest_log = valid_logs[0]
|
|
structure.sudo().write({
|
|
'base_salary': latest_log.base_salary,
|
|
'rank_salary': latest_log.rank_salary,
|
|
'communication_subsidy': latest_log.communication_subsidy,
|
|
'create_time': latest_log.effective_date,
|
|
'position_salary': latest_log.position_salary,
|
|
'advance_performance_bonuses': latest_log.advance_performance_bonuses,
|
|
})
|
|
other_logs = valid_logs - latest_log
|
|
if other_logs:
|
|
other_logs.sudo().write({'is_enable': False})
|
|
|
|
|
|
def action_yuthon_salary(self):
|
|
payroll_ids = self.env["hr.payroll.structure"].browse(
|
|
self._context.get('active_ids', self._context.get('active_id')))
|
|
for structure in payroll_ids:
|
|
if structure.type_id:
|
|
structure.type_id.default_struct_id = structure.id
|
|
|
|
def action_yuthon_salary_wizard(self):
|
|
return {
|
|
"type": "ir.actions.act_window",
|
|
"name": "批量更新工资项",
|
|
"res_model": "yuthon.salary.wizard",
|
|
"view_mode": "form",
|
|
"target": "new",
|
|
}
|
|
|
|
class RankLogLine(models.Model):
|
|
_name = 'rank.log.line'
|
|
_description = '职级日志'
|
|
|
|
structure_id = fields.Many2one('hr.payroll.structure', string='关联职级')
|
|
base_salary = fields.Float(string="基本工资")
|
|
rank_salary = fields.Float(string="职级工资")
|
|
communication_subsidy = fields.Float(string="通讯补贴")
|
|
position_salary = fields.Float(string="岗位工资")
|
|
advance_performance_bonuses = fields.Float(string="月绩效奖金")
|
|
effective_date = fields.Date(string="生效日期")
|
|
lapse_date = fields.Date(string="失效日期")
|
|
is_enable = fields.Boolean(string="启用")
|
|
|
|
|