Files
gzth/yuthon_salary/wizard/salary_compute_wizard.py
T
2025-07-13 20:38:48 +08:00

358 lines
18 KiB
Python

# -*- coding: utf-8 -*-
from odoo import models, fields, api, _, SUPERUSER_ID
from odoo.exceptions import ValidationError, UserError
from odoo.exceptions import ValidationError
from pypinyin import pinyin, Style
import math
import xlrd
import base64
import datetime
from datetime import datetime, timedelta
class SalaryComputeWizard(models.Model):
_name = 'salary.compute.wizard'
_description = '薪资计算弹窗'
employee_ids = fields.Many2many('hr.employee', string='人员', store=True)
company_id = fields.Many2one('res.company', string="公司", default=lambda self: self.env.company)
employee_number = fields.Integer(string="人数")
select_year = fields.Selection(selection=[(str(i), str(i)) for i in range(2024, 2060)], string='年份',
default=lambda self: str(datetime.now().year))
select_time = fields.Selection([('1', '一月'), ('2', '二月'), ('3', '三月'), ('4', '四月'),
('5', '五月'), ('6', '六月'), ('7', '七月'), ('8', '八月'),
('9', '九月'), ('10', '十月'), ('11', '十一月'),
('12', '十二月')], string='月份',
default=lambda self: self._get_default_month())
def _get_default_month(self):
current_month = datetime.now().month
return str(current_month)
@api.onchange('select_year', 'select_time')
def onchange_select_year_select_time(self):
year = int(self.select_year)
month = int(self.select_time)
import calendar
last_day = calendar.monthrange(year, month)[1]
target_date = datetime(year, month, last_day).strftime('%Y-%m-%d')
if self.company_id:
employees = self.env['hr.employee'].search([
('company_id', '=', self.company_id.id),
('is_salary', '=', True),
('entry_date', '<=', target_date),
('contract_id', '!=', False),
'|',
('contract_id.termination_date', '>=', target_date),
'|',
'&',
('contract_id.termination_date', '=', False),
('contract_id.date_end', '>=', target_date),
'&',
('contract_id.termination_date', '=', False),
('contract_id.date_end', '=', False)
])
self.employee_ids = [(6, 0, employees.ids)]
self.employee_number = len(self.employee_ids)
for employee_id in self.employee_ids.ids:
employee = self.env['hr.employee'].browse(employee_id)
# 打印员工姓名
print(f"员工姓名: {employee.name}")
@api.onchange('employee_ids')
def onchange_employee_ids(self):
if self.employee_ids:
self.employee_number = len(self.employee_ids)
def confirm(self):
for employee in self.employee_ids:
detail_id = self.env['hr.attendance.month.detail'].search([('employee_id', '=', employee.id),
('current_year', '=', self.select_year),
('current_month', '=', self.select_time)])
if detail_id.state == 'unconfirmed':
raise UserError(f'{employee.name}的{self.select_time}考勤还未确认,请先确认考勤再进行工资计算!')
is_bold = self.check_employee_dates(employee)
contract_id = self.env['hr.contract'].search([('employee_id', '=', employee.id)], limit=1)
year = int(self.select_year)
month = int(self.select_time)
payslip_run_name = f"{year}年-{self._get_chinese_month(month)}"
start_date = datetime(year, month, 1).strftime('%Y-%m-%d')
if month in [1, 3, 5, 7, 8, 10, 12]:
end_day = 31
elif month in [4, 6, 9, 11]:
end_day = 30
elif (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
end_day = 29
else:
end_day = 28
end_date = datetime(year, month, end_day).strftime('%Y-%m-%d')
payslip_run_id = self.env['hr.payslip.run'].search([('name', '=', payslip_run_name), ('company_id', '=', employee.company_id.id)], limit=1)
if not payslip_run_id:
payslip_run_id = self.env['hr.payslip.run'].create({
'company_id': employee.company_id.id,
'name': payslip_run_name,
'date_start': start_date,
'date_end': end_date,
'state': 'draft',
'payslip_count': 0
})
employee.salary_date = start_date
employee.salary_month = self.select_time
employee.salary_year_jin_ids = [(6, 0, self.employee_ids.ids)]
# 查找有效合同:date_start <= start_date <= date_end(或date_end为空)
contract_id = self.env['hr.contract'].search([
('employee_id', '=', employee.id),
('date_start', '<=', start_date),
'|',
('date_end', '>=', start_date),
('date_end', '=', False),
], limit=1)
existing_payslip = self.env['hr.payslip'].search([
('employee_id', '=', employee.id),
('payslip_run_id', '=', payslip_run_id.id),
], limit=1)
if not existing_payslip:
self.env['hr.payslip'].create({
'is_bold': is_bold,
'name': employee.name,
'employee_id': employee.id,
'contract_id': contract_id.id or employee.contract_id.id,
'date_from': start_date,
'date_to': end_date,
'salary_group_id': contract_id.salary_group_id.id if contract_id else employee.contract_id.salary_group_id.id,
'payslip_run_id': payslip_run_id.id,
'state': 'draft',
}).compute_sheet()
internship_ids = self.env['hr.payslip'].search([
('is_push', '=', False),
('company_id', '=', self.company_id.id),
('date_from', '<=', end_date),
('date_to', '>=', start_date),
])
for internship_id in internship_ids:
employee = internship_id.employee_id
contract = employee.contract_id
# 获取离职日期
turnover_date = employee.contract_id.date_end
start_date = fields.Date.from_string(start_date)
end_date = fields.Date.from_string(end_date)
# 判断员工是否在所选月份已离职
is_turnover = False
if turnover_date:
turnover_year = turnover_date.year
turnover_month = turnover_date.month
# 如果离职年月早于或等于所选年月,则视为已离职
is_turnover = (turnover_year < year) or (turnover_year == year and turnover_month <= month)
# 判断员工是否在试用期
is_probation = False
if contract.start_date and contract.end_date:
# 检查所选月份是否在试用期内
selected_date = fields.Date.to_date(f"{year}-{month:02d}-01")
is_probation = contract.start_date <= selected_date <= contract.end_date
original_start_date = internship_id.date_from
original_end_date = internship_id.date_to
if is_probation:
contract_start_date = contract.start_date
contract_end_date = contract.end_date
elif is_turnover:
contract_start_date = internship_id.date_from
contract_end_date = is_turnover
else:
continue
if is_turnover or is_probation:
days = self._process_shift_days(original_start_date, original_end_date) # 总工作日
if is_turnover:
# 离职日期前的工作日
pre_retirement_days = self._process_shift_days(
start_date,
min(end_date, turnover_date)
)
work_days = pre_retirement_days # 离职前的工作日
elif is_probation:
original_start_date = internship_id.date_from
original_end_date = internship_id.date_to
contract_start_date = internship_id.employee_id.contract_id.start_date
contract_end_date = internship_id.employee_id.contract_id.end_date
# 复制原始日期用于修改
start_date = original_start_date
end_date = original_end_date
# 检查 start_date 是否早于 contract_start_date
if start_date < contract_start_date:
start_date = contract_start_date
internship_days = self._process_shift_days(start_date, end_date) # 实习期工作日
# 重置为原始日期
start_date = original_start_date
end_date = original_end_date
# 检查 end_date 是否晚于 contract_end_date
if end_date > contract_end_date:
end_date = contract_end_date
work_days = self._process_shift_days(start_date, end_date) # 转正工作日
if internship_id.line_ids:
if is_probation:
target_names = {
'基本工资': 'base_salary',
'职级工资': 'rank_salary',
'岗位工资': 'position_salary',
'预发绩效奖金': 'performance_bonuses',
'通讯补贴': 'commuting_allowance',
'工龄工资': 'seniority_salary',
'工会会费': 'union_dues',
'住房公积金': 'housing_provident_fund',
'企业(职业)年金': 'b_year', # 企业年金
}
for line in internship_id.line_ids:
if line.name in target_names:
field_name = target_names[line.name]
if field_name == 'performance_bonuses':
result = 0
elif field_name in ['commuting_allowance', 'seniority_salary']:
if work_days != days:
result = line.total * internship_days / days + line.total * work_days / days
else:
result = line.total * internship_days / days
elif field_name in ['rank_salary', 'base_salary']:
int_value = int(line.total)
ones_digit = int_value % 10
if ones_digit != 0:
rounded_value = int_value + (10 - ones_digit)
else:
rounded_value = int_value
if work_days != days:
result = rounded_value * 0.8 * internship_days / days + rounded_value * work_days / days
else:
result = rounded_value * 0.8 * internship_days / days
elif field_name == 'union_dues': # 单独处理工会会费
base_salary = getattr(internship_id, 'base_salary', 0)
result = base_salary * 0.005
elif field_name == 'housing_provident_fund': # 单独处理住房公积金
# 获取合同入职日期的年份和月份
contract_year = str(contract_start_date.year)
contract_month = str(contract_start_date.month)
# 判断是否为入职当月
if contract_year == self.select_year and contract_month == self.select_time:
result = 0 # 入职当月住房公积金为0
elif field_name == 'b_year':
# 获取合同转正日期的年份和月份
if contract_end_date: # 确保转正日期存在
contract_end_year = str(contract_end_date.year)
contract_end_month = str(contract_end_date.month)
# 判断是否为转正当月
if contract_end_year == self.select_year and contract_end_month == self.select_time:
last_year_salary = internship_id.employee_id.last_year_salary or 0
result = last_year_salary * 0.002
else:
result = 0
else:
if work_days != days:
result = line.total * 0.8 * internship_days / days + line.total * work_days / days
else:
result = line.total * 0.8 * internship_days / days
setattr(internship_id, field_name, result)
line.write({'total': result})
if is_turnover:
target_names = {
'基本工资': 'base_salary',
'岗位工资': 'position_salary',
'通讯补贴': 'commuting_allowance',
'综合补贴': 'comprehensive_subsidy',
'全勤奖': 'full_attendance_award',
}
for line in internship_id.line_ids:
if line.name in target_names:
field_name = target_names[line.name]
# 统一按公式计算:金额/21.75 * 离职前工作日数量,并保留两位小数
result = round((line.total / 21.75) * work_days, 2)
setattr(internship_id, field_name, result)
line.write({'total': result})
# for rec in self.env['hr.payslip'].search([('is_push', '=', False), ('employee_id.name', '!=', '王鸿怡')]):
# rec.compute_sheet()
#
# for rec2 in self.env['hr.payslip'].search([('employee_id.name', 'in', ['黄昱皓', '谭铭浩', '朱文浩'])]):
# rec2.compute_sheet()
def _get_chinese_month(self, month):
chinese_months = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
return chinese_months[month - 1]
def _process_shift_days(self, start_date, end_date):
calendar_leaves = self.env['resource.calendar.leaves'].search([("calendar_id", "=", "行政考勤工作日")])
holidays_list = []
shift_days_list = []
for leaves in calendar_leaves:
if leaves.holidays:
holidays_list.extend(leaves.holidays.split(','))
if leaves.alternate_rest_day:
holidays_list.extend(leaves.alternate_rest_day.split(','))
if leaves.shift_replacement_day:
shift_days_list.extend(leaves.shift_replacement_day.split(','))
days = (end_date - start_date).days + 1
current_date = start_date
while current_date <= end_date:
current_date_str = current_date.strftime('%m月%d日')
current_date_str = self.remove_leading_zero(current_date_str)
is_weekend = current_date.weekday() in [5, 6]
if current_date_str in holidays_list:
days -= 1
elif current_date_str in shift_days_list:
if not is_weekend:
days += 1
elif is_weekend:
days -= 1
current_date += timedelta(days=1)
return days
def remove_leading_zero(self, date_str):
month, day = date_str.split('月')
month = month.lstrip('0')
day = day.rstrip('日')
day = day.lstrip('0')
return f'{month}月{day}日'
def check_employee_dates(self, employee):
"""检查员工为试用期入职日期和转正日期的月份 和退休(花名册)的是否等于 select_time 的月份"""
if not self.select_time:
return False # 未选择月份时返回 False
selected_month = int(self.select_time)
selected_year = int(self.select_year)
if employee.contract_id.is_probation:
entry_matches = False
if employee.entry_date:
entry_matches = (
employee.entry_date.month == selected_month and
employee.entry_date.year == selected_year
)
confirmation_matches = False
if employee.confirmation_date:
confirmation_matches = (
employee.confirmation_date.month == selected_month and
employee.confirmation_date.year == selected_year
)
# 检查退休日期
retire_matches = False
if employee.retire_date:
retire_matches = (
employee.retire_date.month == selected_month and
employee.retire_date.year == selected_year
)
# 满足任一条件即返回 True
return entry_matches or confirmation_matches or retire_matches