358 lines
18 KiB
Python
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 |