877 lines
42 KiB
Python
877 lines
42 KiB
Python
from odoo import api, fields, models, _
|
||
from odoo.exceptions import ValidationError, UserError
|
||
import json
|
||
from odoo.tools import date_utils
|
||
from dateutil.relativedelta import relativedelta
|
||
import re
|
||
from datetime import datetime
|
||
import logging
|
||
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
|
||
class InheritHrPayslip(models.Model):
|
||
_inherit = 'hr.payslip'
|
||
|
||
company_id = fields.Many2one('res.company', string="公司", default=lambda self: self.env.company)
|
||
employee_id = fields.Many2one('hr.employee', string="姓名")
|
||
date_of_entry = fields.Date(related='employee_id.entry_date', string="入职时间", store=True)
|
||
base_salary = fields.Float(string='基本工资', compute='_compute_salary', store=True)
|
||
bank_account_id = fields.Many2one(string='银行账号', related='employee_id.bank_account_id', store=True)
|
||
tax_payable = fields.Float(string='应补(退)税额')
|
||
actual_salary = fields.Float(string='实发工资', store=True, compute='_compute_actual_salary')
|
||
three_one = fields.Float(string='三险一金', store=True, compute='_compute_salary')
|
||
identification_id = fields.Char(string='证件号码', related='employee_id.identification_id', store=True)
|
||
card_type = fields.Char(string='证件类型', store=True, default="居民身份证")
|
||
salary_group_id = fields.Many2one('yuthon.salary.group', string="工资组", store=True, index=True)
|
||
performance_bonuses = fields.Float(string='预发绩效奖金', compute='_compute_salary', store=True)
|
||
full_attendance_award = fields.Float(string='全勤奖', compute='_compute_salary', store=True)
|
||
commuting_allowance = fields.Float(string='通信补贴', compute='_compute_salary', store=True)
|
||
position_subsidy = fields.Monetary(string="岗位补贴", compute="_compute_salary", store=True)
|
||
position_salary = fields.Monetary(string="岗位工资", compute="_compute_salary", store=True)
|
||
rank_salary = fields.Float(string='职级工资', compute='_compute_salary', store=True)
|
||
housing_loan_interest = fields.Float(string='累计住房贷款利息')
|
||
elderly_support_deduction = fields.Float(string='累计个人养老金')
|
||
housing_rent = fields.Float(string='累计住房租金')
|
||
child_education = fields.Float(string='累计子女教育')
|
||
tax = fields.Float(string="税金")
|
||
retroactive_allowance = fields.Float(string="补发津贴")
|
||
condolence_fund = fields.Float(string="儿童节慰问金")
|
||
continuing_education = fields.Float(string='累计继续教育')
|
||
elderly_support = fields.Float(string='累计赡养老人')
|
||
infant_care = fields.Float(string='累计3岁以下婴幼儿照护')
|
||
seniority_salary = fields.Float(string='工龄工资', compute='_compute_salary', store=True)
|
||
union_dues = fields.Float(string='工会会费')
|
||
overtime_pay = fields.Float(string='加班费', compute='_compute_salary', store=True)
|
||
meal_misunderstanding_fee = fields.Float(string='误餐费', compute='_compute_salary', store=True)
|
||
personal_leave = fields.Float(string='事假', compute='_compute_salary', store=True)
|
||
sick_leave = fields.Float(string='病假', compute='_compute_salary', store=True)
|
||
maternity_leave = fields.Float(string='产假', compute='_compute_salary', store=True)
|
||
marriage_leave = fields.Float(string='婚假', compute='_compute_salary', store=True)
|
||
family_planning_leave = fields.Float(string='计生假', compute='_compute_salary', store=True)
|
||
bereavement_leave = fields.Float(string='丧假', compute='_compute_salary', store=True)
|
||
home_visit_leave = fields.Float(string='探亲假', compute='_compute_salary', store=True)
|
||
hospital_sick_leave = fields.Float(string='住院病假', compute='_compute_salary', store=True)
|
||
exam_leave = fields.Float(string='考试假', compute='_compute_salary', store=True)
|
||
parents_meeting_leave = fields.Float(string='家长会假', compute='_compute_salary', store=True)
|
||
basic_medical_insurance = fields.Float(string='基本医疗保险费', compute='_compute_salary', store=True)
|
||
basic_endowment_insurance = fields.Float(string='基本养老保险费', compute='_compute_salary', store=True)
|
||
unemployment_insurance = fields.Float(string='失业保险费', compute='_compute_salary', store=True)
|
||
work_injury_insurance = fields.Float(string='工伤保险费', compute='_compute_salary', store=True)
|
||
housing_provident_fund = fields.Float(string='住房公积金', compute='_compute_salary', store=True)
|
||
b_year = fields.Float(string='企业(职业)年金', compute='_compute_salary', store=True)
|
||
total_before_tax = fields.Float(string='本期应收', compute='_compute_total_before_tax', store=True)
|
||
year_compute = fields.Float(string='前年年度清算', store=True)
|
||
year_jie = fields.Float(string='前年年度结余', store=True)
|
||
children_welfare_fee = fields.Float(string='儿童节慰问金') # 天河
|
||
year_gong = fields.Float(string='年功奖', store=True)
|
||
study_on = fields.Float(string='学习进步奖')
|
||
jing_te = fields.Float(string='经营特别奖')
|
||
temporary_performance_bonus = fields.Float(string='临时绩效奖')
|
||
last_year_safety = fields.Float(string='上年安全生产工作奖')
|
||
last_year_jie = fields.Float(string='上年年度结余')
|
||
last_year_performance_evaluation = fields.Float(string='上年绩效评优')
|
||
retention_previous_year = fields.Float(string='上年考核保留量')
|
||
social_security = fields.Float(string='上年社保补缴')
|
||
performance_up = fields.Float(string='上月多扣绩效补回')
|
||
individual_tax_refund = fields.Float(string='上年个税退回')
|
||
liquidation_year = fields.Float(string='上年年度清算')
|
||
safety_incentives = fields.Float(string='安全生产奖')
|
||
is_push = fields.Boolean(string='推送')
|
||
is_salary_push = fields.Boolean(string='工资推送')
|
||
is_salary_release = fields.Boolean(string='薪资发放')
|
||
salary_state = fields.Selection([('to_signed', '待签字' ), ('to_release', '待发放'), ('released', '已发放')], string='状态', default='to_signed')
|
||
commercial_health_insurance = fields.Float(string='商业健康保险')
|
||
tax_deferred_pension_insurance = fields.Float(string='税延养老保险')
|
||
others = fields.Float(string='其他扣除')
|
||
deductible_donations = fields.Float(string='准予扣除的捐赠额')
|
||
total_pre_tax_deductions = fields.Float(string='税前扣除项目合计')
|
||
tax_reduction = fields.Float(string='减免税额')
|
||
deduction_standard = fields.Float(string='减除费用标准')
|
||
paid_tax = fields.Float(string='已缴税额')
|
||
based_pay = fields.Float(string='临时绩效工资')
|
||
life_salary = fields.Float(string='生活补贴')
|
||
january_performance = fields.Float(string='1月份绩效奖金补发')
|
||
accumulated_underpayment = fields.Float(string='累计欠收')
|
||
cumulative_income = fields.Float(string='其他单位累计收入')
|
||
other_cum_deductions = fields.Float(string='其他单位累计扣除')
|
||
other_cumulative_deductions = fields.Float(string='其他单位累计减免税额')
|
||
other_cumulative_tax_paid = fields.Float(string='其他单位累计已缴税额')
|
||
cumulative_taxable = fields.Float(string='累计应纳税所得额')
|
||
quick_deductions = fields.Float(string='速算扣除数')
|
||
payable = fields.Float(string='累计应纳税额')
|
||
tariff = fields.Float(string='税率')
|
||
cumulative_deductions = fields.Float(string='累计减免税额')
|
||
amount_tax_withheld = fields.Float(string='累计应扣缴税额')
|
||
is_bold = fields.Boolean()
|
||
confirm_status = fields.Selection([('confirm', '已确认'), ('un_confirm', '未确认')], string='确认状态', default='un_confirm')
|
||
remake = fields.Text(string='备注')
|
||
|
||
# 物管常用项
|
||
seniority_allowance = fields.Float(string='工龄补贴')
|
||
safety_subsidy = fields.Float(string='安全生产管理补贴')
|
||
job_performance = fields.Float(string='岗位绩效')
|
||
comprehensive_subsidy = fields.Float(string='综合补贴')
|
||
duty = fields.Float(string='值班(含节日加班)')
|
||
monthly_overtime = fields.Float(string='月加班')
|
||
annuity = fields.Float(string='年金')
|
||
spring_festival = fields.Float(string='春节福利')
|
||
|
||
# 物管临时项
|
||
excellent_employees = fields.Float(string='优秀部门经理/员工奖励')
|
||
|
||
# 粮储常用项
|
||
quality_allowance = fields.Float(string='质检员津贴', compute="_compute_salary", store=True)
|
||
environmental_allowance = fields.Float(string='环境作业津贴')
|
||
last_duty = fields.Float(string='上月值班费')
|
||
high_subsidy = fields.Float(string='高温补贴')
|
||
|
||
# 粮储临时项
|
||
medical_fee = fields.Float(string='超出体检费标准')
|
||
retroactive_insurance = fields.Float(string='补扣保险')
|
||
distribution_part = fields.Float(string='扣除环境作业津贴多发放部分')
|
||
|
||
# 三家公司
|
||
add_year_float = fields.Float(string='补年金差额')
|
||
individual_income_tax = fields.Float(string='个税扣发(减)')
|
||
add_income_tax = fields.Float(string='个税补发(增)')
|
||
maternity_allowance = fields.Float(string='生育津贴')
|
||
minus_annuity_difference = fields.Float(string='年金差额(减)')
|
||
add_annuity_difference = fields.Float(string='年金差额(增)')
|
||
minus_att = fields.Float(string='考勤扣发(减)')
|
||
add_att = fields.Float(string='考勤补发(增)')
|
||
minus_year_float = fields.Float(string='扣年金差额')
|
||
|
||
# 五险一金
|
||
# 基本医疗
|
||
basic_pension_base = fields.Float(string='缴费基数')
|
||
basic_pension_payable = fields.Float(string='应缴金额')
|
||
one_basic_pension_base = fields.Float(string='缴费基数')
|
||
one_basic_pension_payable = fields.Float(string='应缴金额')
|
||
# 失业
|
||
employment_insurance = fields.Float(string='缴费基数')
|
||
employment_insurance_payable = fields.Float(string='应缴金额')
|
||
one_employment_insurance = fields.Float(string='缴费基数')
|
||
one_employment_insurance_payable = fields.Float(string='应缴金额')
|
||
# 医疗
|
||
medical_insurance_base = fields.Float(string='缴费基数')
|
||
medical_insurance_payable = fields.Float(string='应缴金额')
|
||
one_medical_insurance_base = fields.Float(string='缴费基数')
|
||
one_medical_insurance_payable = fields.Float(string='应缴金额')
|
||
# 工伤
|
||
injury_insurance = fields.Float(string='缴费基数')
|
||
injury_insurance_payable = fields.Float(string='应缴金额')
|
||
# 合计
|
||
total_unit_part = fields.Float(string='单位部分合计')
|
||
total_personal_part = fields.Float(string='个人部分合计')
|
||
total_payable = fields.Float(string='应缴金额合计')
|
||
|
||
performance_deduction = fields.Float(string='扣发绩效奖金')
|
||
commuting_deduction = fields.Float(string='扣发通信补贴', store=True)
|
||
base_salary_deduction = fields.Float(string='扣发基本工资', store=True)
|
||
specialized_deduction = fields.Float(string='累计专项扣除')
|
||
|
||
@api.onchange('minus_att', 'individual_income_tax', 'minus_annuity_difference')
|
||
def _onchange_ensure_negative(self):
|
||
for record in self:
|
||
if record.minus_att > 0:
|
||
record.minus_att = -record.minus_att
|
||
if record.individual_income_tax > 0:
|
||
record.individual_income_tax = -record.individual_income_tax
|
||
if record.minus_annuity_difference > 0:
|
||
record.minus_annuity_difference = -record.minus_annuity_difference
|
||
|
||
def _process_fields(self, values):
|
||
fields_to_process = [
|
||
'minus_annuity_difference',
|
||
'individual_income_tax',
|
||
'minus_att',
|
||
'retroactive_insurance',
|
||
'tax_payable',
|
||
'one_basic_pension_payable',
|
||
'employment_insurance_payable',
|
||
'basic_pension_payable',
|
||
'medical_insurance_payable',
|
||
'one_employment_insurance_payable',
|
||
'one_medical_insurance_base',
|
||
'social_security',
|
||
'injury_insurance_payable',
|
||
'total_unit_part',
|
||
'total_personal_part',
|
||
'total_payable',
|
||
'one_medical_insurance_payable',
|
||
'performance_deduction',
|
||
'commuting_deduction',
|
||
'base_salary_deduction'
|
||
]
|
||
for field in fields_to_process:
|
||
if field in values:
|
||
# 处理逻辑:取绝对值后减1
|
||
values[field] = abs(values[field]) * -1
|
||
return values
|
||
|
||
def write(self, values):
|
||
values = self._process_fields(values)
|
||
return super(InheritHrPayslip, self).write(values)
|
||
|
||
def disposable(self):
|
||
# 处理2025年1月至5月的数据
|
||
year = 2025
|
||
# 要处理的月份范围
|
||
months = range(1, 6) # 1到5月
|
||
for month in months:
|
||
payslip_run_name = f"{year}年-{self._get_chinese_month(month)}"
|
||
payslip_run = self.env['hr.payslip.run'].search(
|
||
[('name', '=', payslip_run_name)],
|
||
limit=1
|
||
)
|
||
if payslip_run:
|
||
# 查找该批次下的所有工资单记录
|
||
payslips = self.env['hr.payslip'].search([
|
||
('payslip_run_id', '=', payslip_run.id)
|
||
])
|
||
|
||
if payslips:
|
||
# 定义需要处理的所有字段(用于触发write)
|
||
all_fields = [
|
||
'minus_annuity_difference',
|
||
'individual_income_tax',
|
||
'minus_att',
|
||
'retroactive_insurance',
|
||
'tax_payable',
|
||
'one_basic_pension_payable',
|
||
'employment_insurance_payable',
|
||
'basic_pension_payable',
|
||
'medical_insurance_payable',
|
||
'one_employment_insurance_payable',
|
||
'one_medical_insurance_base',
|
||
'social_security',
|
||
'injury_insurance_payable',
|
||
'total_unit_part',
|
||
'total_personal_part',
|
||
'total_payable',
|
||
'one_medical_insurance_payable',
|
||
]
|
||
|
||
fields_to_modify = [
|
||
'housing_provident_fund',
|
||
'union_dues'
|
||
'basic_medical_insurance',
|
||
'basic_endowment_insurance',
|
||
'unemployment_insurance',
|
||
'work_injury_insurance',
|
||
'three_one',
|
||
]
|
||
|
||
# 处理每个工资单
|
||
for payslip in payslips:
|
||
# 只修改指定的两个字段
|
||
for field in fields_to_modify:
|
||
if hasattr(payslip, field) and getattr(payslip, field) is not None:
|
||
original_value = getattr(payslip, field)
|
||
new_value = abs(original_value) * -1
|
||
setattr(payslip, field, new_value)
|
||
|
||
for payslip in payslips:
|
||
write_vals = {}
|
||
for field in all_fields:
|
||
if hasattr(payslip, field):
|
||
write_vals[field] = getattr(payslip, field)
|
||
payslip.write(write_vals)
|
||
|
||
def _get_chinese_month(self, month):
|
||
chinese_months = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月',
|
||
'十二月']
|
||
return chinese_months[month - 1]
|
||
|
||
@api.onchange('line_ids')
|
||
def _onchange_line_ids(self):
|
||
for line in self.line_ids:
|
||
negative_items = [
|
||
'考勤扣发(减)',
|
||
'个税扣发(减)',
|
||
'年金差额(减)',
|
||
'扣发绩效奖金',
|
||
'三险一金',
|
||
'企业(职业)年金',
|
||
'应补(退)税额',
|
||
'工会会费',
|
||
'补扣保险',
|
||
]
|
||
if line.name in negative_items and line.total > 0:
|
||
line.total = -line.total
|
||
|
||
@api.onchange('payslip_run_id')
|
||
def _onchange_payslip_run_id(self):
|
||
if not self.payslip_run_id:
|
||
self.date_from = False
|
||
self.date_to = False
|
||
return
|
||
batch_name = self.payslip_run_id.name
|
||
pattern = r'(\d{4})年-([一二三四五六七八九十]{1,2})月'
|
||
match = re.search(pattern, batch_name)
|
||
if match:
|
||
year = int(match.group(1))
|
||
month_name = match.group(2)
|
||
month_mapping = {
|
||
'一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6,
|
||
'七': 7, '八': 8, '九': 9, '十': 10, '十一': 11, '十二': 12
|
||
}
|
||
month = month_mapping.get(month_name)
|
||
if month:
|
||
self.date_from = datetime(year, month, 1).date()
|
||
self.date_to = (datetime(year, month, 1) + relativedelta(months=1, days=-1)).date()
|
||
|
||
|
||
@api.model_create_multi
|
||
def create(self, vals_list):
|
||
# 存储每个工资单的检查结果
|
||
existing_payslips = {}
|
||
for vals in vals_list:
|
||
employee_id = vals.get('employee_id')
|
||
payslip_run_id = vals.get('payslip_run_id')
|
||
if not employee_id or not payslip_run_id:
|
||
continue
|
||
# 检查工资单是否存在于各个状态
|
||
conditions = [
|
||
(_('薪资审定'), [
|
||
('employee_id', '=', employee_id),
|
||
('payslip_run_id', '=', payslip_run_id),
|
||
('is_salary_push', '=', True),
|
||
('is_salary_release', '=', False)
|
||
]),
|
||
(_('薪资归档'), [
|
||
('employee_id', '=', employee_id),
|
||
('payslip_run_id', '=', payslip_run_id),
|
||
('is_salary_push', '=', True),
|
||
('is_salary_release', '=', True)
|
||
]),
|
||
(_('薪资计算'), [
|
||
('employee_id', '=', employee_id),
|
||
('payslip_run_id', '=', payslip_run_id),
|
||
('is_push', '=', False)
|
||
]),
|
||
(_('个税计算'), [
|
||
('employee_id', '=', employee_id),
|
||
('payslip_run_id', '=', payslip_run_id),
|
||
('is_push', '=', True),
|
||
('is_salary_push', '=', False)
|
||
])
|
||
]
|
||
|
||
for state_name, domain in conditions:
|
||
existing = self.search(domain, limit=1)
|
||
if existing:
|
||
# 获取员工和批次名称用于错误提示
|
||
employee = self.env['hr.employee'].browse(employee_id)
|
||
payslip_run = self.env['hr.payslip.run'].browse(payslip_run_id)
|
||
|
||
existing_payslips[(employee_id, payslip_run_id)] = {
|
||
'employee_name': employee.name,
|
||
'batch_name': payslip_run.name,
|
||
'state': state_name
|
||
}
|
||
break
|
||
if existing_payslips:
|
||
error_messages = []
|
||
for key, info in existing_payslips.items():
|
||
msg = _("员工 %(employee)s 在批次 '%(batch)s' 的工资单已存在于'%(state)s'中!") % {
|
||
'employee': info['employee_name'],
|
||
'batch': info['batch_name'],
|
||
'state': info['state']
|
||
}
|
||
error_messages.append(msg)
|
||
raise UserError('\n'.join(error_messages))
|
||
res = super(InheritHrPayslip, self).create(vals_list)
|
||
return res
|
||
|
||
@api.model
|
||
def create(self, vals):
|
||
"""
|
||
为了解决部分薪资生成未生效,单独处理计算公式
|
||
工会会费:round(abs(payslip.base_salary * 0.005) * -1, 2) --union_dues
|
||
:param vals:
|
||
:return:
|
||
"""
|
||
record = super().create(vals)
|
||
employee_id = self.env['hr.employee'].search([('id', '=', vals['employee_id'])])
|
||
payroll = self.env['hr.contract'].search([('employee_id', '=', employee_id.id), ('state', '=', 'open')])[-1].structure_type_id.default_struct_id
|
||
if payroll.rule_ids.search([('name', '=', '基本工资'), ('struct_id', '=', payroll.id)]):
|
||
base_salary = payroll.rule_ids.search([('name', '=', '基本工资'), ('struct_id', '=', payroll.id)])[-1].amount_fix
|
||
# if record.worked_days_line_ids.search([('name', '=', '工会会费')]):
|
||
# record.line_ids.search([('name', '=', '工会会费')]).total = round(abs(base_salary * 0.005) * -1, 2)
|
||
record.write({'union_dues': round(abs(base_salary * 0.005) * -1, 2)})
|
||
return record
|
||
|
||
@api.ondelete(at_uninstall=False)
|
||
def _unlink_if_draft_or_cancel(self):
|
||
if any(payslip.state not in ('draft', 'cancel') for payslip in self):
|
||
raise UserError(_('确定是否删除?手工填写的临时工资项数据也将被删除!\n 工资条只有草稿和取消状态才可以删除,请批量取消后操作'))
|
||
|
||
def action_refresh_from_work_entries(self):
|
||
payslip_records = self.browse(self._context.get('active_ids', self._context.get('active_id')))
|
||
payslips = payslip_records.filtered(lambda slip: slip.employee_id.name != '王鸿怡')
|
||
for rec in payslips:
|
||
rec.compute_sheet()
|
||
|
||
@api.onchange('employee_id')
|
||
def _onchange_salary_group_id(self):
|
||
self.salary_group_id = self.employee_id.contract_id.salary_group_id
|
||
|
||
@api.depends('line_ids', 'social_security')
|
||
def _compute_salary(self):
|
||
for rec in self:
|
||
if rec.line_ids:
|
||
for line in rec.line_ids:
|
||
if line.name == '学习进步奖':
|
||
rec.study_on = line.total
|
||
if line.name == '临时绩效奖金':
|
||
rec.temporary_performance_bonus = line.total
|
||
if line.name == '上年安全生产工作奖':
|
||
rec.last_year_safety = line.total
|
||
if line.name == '生育津贴':
|
||
rec.allowance = line.total
|
||
if line.name == '儿童节慰问金':
|
||
rec.condolence_fund = line.total
|
||
if line.name == '春节福利':
|
||
rec.spring_festival = line.total
|
||
if line.name == '临时绩效工资':
|
||
rec.based_pay = line.total
|
||
if line.name == '上年绩效评优':
|
||
rec.last_year_performance_evaluation = line.total
|
||
if line.name == '上年社保补缴':
|
||
rec.social_security = line.total
|
||
if line.name == '上年年度清算':
|
||
rec.liquidation_year = line.total
|
||
if line.name == '年功奖':
|
||
rec.year_gong = line.total
|
||
if line.name == '生活补贴':
|
||
rec.life_salary = line.total
|
||
if line.name == '安全生产奖':
|
||
rec.safety_incentives = line.total
|
||
if line.name == '前年年度清算':
|
||
rec.year_compute = line.total
|
||
if line.name == '前年年度结余':
|
||
rec.year_jie = line.total
|
||
if line.name == '上月多扣绩效补回':
|
||
rec.performance_up = line.total
|
||
if line.name == '上年考核保留量':
|
||
rec.retention_previous_year = line.total
|
||
if line.name == '上年年度结余':
|
||
rec.last_year_jie = line.total
|
||
if line.name == '上年个税退回':
|
||
rec.individual_tax_refund = line.total
|
||
if line.name == '经营特别奖':
|
||
rec.jing_te = line.total
|
||
if line.name == '补发津贴':
|
||
rec.retroactive_allowance = line.total
|
||
if line.name == '个税扣发(减)':
|
||
rec.individual_income_tax = line.total
|
||
if line.name == '个税补发(增)':
|
||
rec.add_income_tax = line.total
|
||
if line.name == '年金差额(减)':
|
||
rec.minus_annuity_difference = line.total
|
||
if line.name == '年金差额(增)':
|
||
rec.add_annuity_difference = line.total
|
||
if line.name == '考勤扣发(减)':
|
||
rec.minus_att = line.total
|
||
if line.name == '考勤补发(增)':
|
||
rec.add_att = line.total
|
||
if line.name == '综合补贴':
|
||
rec.comprehensive_subsidy = line.total
|
||
if line.name == '安全生产管理补贴':
|
||
rec.safety_subsidy = line.total
|
||
if line.name == '职级工资':
|
||
rec.rank_salary = line.total
|
||
if line.name == '岗位补贴':
|
||
rec.position_subsidy = line.total
|
||
if line.name == '工龄补贴':
|
||
rec.seniority_allowance = line.total
|
||
if line.name == '岗位工资':
|
||
rec.position_salary = line.total
|
||
if line.name == '基本工资':
|
||
rec.base_salary = line.total
|
||
if line.name == '预发绩效奖金':
|
||
rec.performance_bonuses = line.total
|
||
if line.name == '工龄工资':
|
||
rec.seniority_salary = line.total
|
||
if line.name == '通讯补贴':
|
||
rec.commuting_allowance = line.total
|
||
if line.name == '全勤奖':
|
||
rec.full_attendance_award = line.total
|
||
if line.name == '加班费':
|
||
rec.overtime_pay = line.total
|
||
if line.name == '质检员津贴':
|
||
rec.quality_allowance = line.total
|
||
if line.name == '误餐费':
|
||
rec.meal_misunderstanding_fee = line.total
|
||
if line.name == '事假':
|
||
rec.personal_leave = line.total
|
||
if line.name == '病假':
|
||
rec.sick_leave = line.total
|
||
if line.name == '产假':
|
||
rec.maternity_leave = line.total
|
||
if line.name == '婚假':
|
||
rec.marriage_leave = line.total
|
||
if line.name == '上月值班费':
|
||
rec.last_duty = line.total
|
||
if line.name == '计生假':
|
||
rec.family_planning_leave = line.total
|
||
if line.name == '丧假':
|
||
rec.bereavement_leave = line.total
|
||
if line.name == '探亲假':
|
||
rec.home_visit_leave = line.total
|
||
if line.name == '住院病假':
|
||
rec.hospital_sick_leave = line.total
|
||
if line.name == '考试假':
|
||
rec.exam_leave = line.total
|
||
if line.name == '企业(职业)年金':
|
||
rec.b_year = line.total
|
||
if line.name == '家长会假':
|
||
rec.parents_meeting_leave = line.total
|
||
if line.name == '基本医疗保险费':
|
||
rec.basic_medical_insurance = line.total
|
||
if line.name == '基本养老保险费':
|
||
rec.basic_endowment_insurance = line.total
|
||
if line.name == '失业保险费':
|
||
rec.unemployment_insurance = line.total
|
||
if line.name == '工伤保险费':
|
||
rec.work_injury_insurance = line.total
|
||
if line.name == '住房公积金':
|
||
rec.housing_provident_fund = line.total
|
||
if line.name == '扣发绩效奖金':
|
||
rec.performance_deduction = line.total
|
||
if line.name == '扣发通信补贴':
|
||
rec.commuting_deduction = line.total
|
||
if line.name == '扣发基本工资':
|
||
rec.base_salary_deduction = line.total
|
||
if line.name == '优秀部门经理/员工奖励':
|
||
rec.excellent_employees = line.total
|
||
if line.name == '环境作业津贴':
|
||
rec.environmental_allowance = line.total
|
||
if line.name == '高温补贴':
|
||
rec.high_subsidy = line.total
|
||
if line.name == '超出体检标准的费用':
|
||
rec.medical_fee = line.total
|
||
if line.name == '补扣保险':
|
||
rec.retroactive_insurance = line.total
|
||
if line.name == '扣除环境作业津贴多发放部分':
|
||
rec.distribution_part = line.total
|
||
if line.name == '岗位绩效':
|
||
rec.job_performance = line.total
|
||
if line.name == '值班(含节日加班)':
|
||
rec.duty = line.total
|
||
if line.name == '月加班':
|
||
rec.monthly_overtime = line.total
|
||
if line.name == '年金':
|
||
rec.annuity = line.total
|
||
|
||
rec.three_one = rec.one_basic_pension_payable + rec.one_employment_insurance_payable + rec.one_medical_insurance_payable + rec.social_security + rec.housing_provident_fund
|
||
|
||
def basic_pension_wizard(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "五险一金导入",
|
||
"res_model": "basic.pension.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
}
|
||
|
||
|
||
def put_payslip(self):
|
||
menu_id = self.env['ir.ui.menu'].search([('name', '=', '工资条'), ('parent_id', '!=', False)])
|
||
url = f'https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=hr.payslip&view_type=list&menu_id={menu_id.id}'
|
||
we_id = self.env['hr.employee'].search([('id', '=', self.employee_id)]).wecom_userid
|
||
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='payslip', user_id=we_id, title="薪资确认",
|
||
description=self.employee_id.name + '请确认你的薪资:',
|
||
url=url, btntxt="详细信息")
|
||
return {
|
||
'type': 'ir.actions.client',
|
||
'tag': 'display_notification',
|
||
'params': {
|
||
'type': 'success',
|
||
'message': '确认通知已发送',
|
||
'sticky': False,
|
||
}
|
||
}
|
||
|
||
def payslip_wizard(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "工资条推送",
|
||
"res_model": "payslip.send.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
"context": {
|
||
'default_employee_id': self.id.employee_id.id
|
||
}
|
||
}
|
||
|
||
def action_salary_payslip_wizard(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "累计专项导入",
|
||
"res_model": "salary.payslip.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
}
|
||
|
||
def action_temporary_payslip_wizard(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "临时工资项导入",
|
||
"res_model": "temporary.payslip.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
}
|
||
|
||
@api.depends('year_compute', 'year_jie', 'year_gong', 'jing_te', 'study_on', 'seniority_allowance',
|
||
'position_salary', 'liquidation_year', 'rank_salary',
|
||
'quality_allowance', 'high_subsidy', 'environmental_allowance', 'last_duty', 'seniority_salary',
|
||
'commuting_allowance', 'base_salary', 'performance_bonuses', 'comprehensive_subsidy',
|
||
'full_attendance_award', 'overtime_pay', 'based_pay',
|
||
'january_performance', 'position_subsidy', 'safety_incentives', 'temporary_performance_bonus',
|
||
'performance_deduction', 'commuting_deduction', 'retroactive_insurance',
|
||
'base_salary_deduction', 'last_year_performance_evaluation', 'safety_subsidy', 'job_performance',
|
||
'distribution_part', 'minus_att', 'add_att', 'spring_festival',
|
||
'duty', 'monthly_overtime', 'annuity', 'excellent_employees', 'condolence_fund',
|
||
'retention_previous_year')
|
||
def _compute_total_before_tax(self):
|
||
for record in self:
|
||
record.total_before_tax = (
|
||
record.rank_salary +
|
||
record.condolence_fund +
|
||
record.spring_festival +
|
||
record.liquidation_year +
|
||
record.add_att +
|
||
record.year_compute +
|
||
record.year_jie +
|
||
record.year_gong +
|
||
record.jing_te +
|
||
record.study_on +
|
||
record.safety_incentives +
|
||
record.temporary_performance_bonus +
|
||
record.last_year_performance_evaluation +
|
||
record.retention_previous_year +
|
||
record.based_pay +
|
||
record.january_performance -
|
||
record.distribution_part +
|
||
record.seniority_allowance +
|
||
record.position_salary +
|
||
record.quality_allowance +
|
||
record.high_subsidy +
|
||
record.environmental_allowance +
|
||
record.last_duty +
|
||
record.seniority_salary +
|
||
record.commuting_allowance +
|
||
record.base_salary +
|
||
record.performance_bonuses +
|
||
record.comprehensive_subsidy +
|
||
record.full_attendance_award +
|
||
record.overtime_pay +
|
||
record.position_subsidy +
|
||
record.safety_subsidy +
|
||
record.job_performance +
|
||
record.minus_att +
|
||
record.duty +
|
||
record.performance_deduction +
|
||
record.monthly_overtime +
|
||
record.excellent_employees +
|
||
record.commuting_deduction +
|
||
record.retroactive_insurance +
|
||
record.base_salary_deduction
|
||
)
|
||
|
||
|
||
# 本期应收 - 三险一金 - 年金 - 应补退税 - 工会会费 - 个税退回
|
||
# total_before_tax - three_one - b_year - union_dues - tax_payable - individual_tax_refund
|
||
@api.depends('total_before_tax', 'three_one', 'b_year', 'union_dues', 'tax_payable', 'individual_tax_refund',
|
||
'medical_fee', 'individual_income_tax', 'add_income_tax', 'minus_annuity_difference',
|
||
'add_annuity_difference', 'maternity_allowance')
|
||
def _compute_actual_salary(self):
|
||
for record in self:
|
||
record.actual_salary = (record.total_before_tax +
|
||
record.three_one -
|
||
record.social_security -
|
||
record.medical_fee +
|
||
record.b_year +
|
||
record.union_dues +
|
||
record.tax_payable +
|
||
record.maternity_allowance +
|
||
record.individual_income_tax +
|
||
record.add_income_tax +
|
||
record.minus_annuity_difference +
|
||
record.individual_tax_refund +
|
||
record.life_salary +
|
||
record.add_annuity_difference
|
||
)
|
||
if record.employee_id.name == '罗梦烨':
|
||
_logger.info("罗梦烨实发{} 本期应:{},31:{},年金:{}应补:{}工会{}个税{}".format(record.actual_salary, record.total_before_tax,
|
||
record.three_one, record.b_year, record.union_dues,
|
||
record.tax_payable, record.individual_tax_refund))
|
||
|
||
def put_button(self):
|
||
for record in self:
|
||
record.write({'is_push': True})
|
||
|
||
def return_button(self):
|
||
for record in self:
|
||
record.write({'is_push': False})
|
||
|
||
def put_salary_button(self):
|
||
for record in self:
|
||
record.write({'is_salary_push': True})
|
||
|
||
def action_put_wizard(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "推送个税计算",
|
||
"res_model": "put.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
}
|
||
|
||
def action_return_wizard(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "退回薪资计算",
|
||
"res_model": "return.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
}
|
||
|
||
def action_put_salary_wizard(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "推送薪资审定",
|
||
"res_model": "put.salary.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
}
|
||
|
||
def action_return_salary_wizard(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "退回个税计算",
|
||
"res_model": "return.salary.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
}
|
||
|
||
def compute_sheet(self):
|
||
payslips = self.filtered(lambda slip: slip.state in ['draft', 'verify'])
|
||
payslips.line_ids.unlink()
|
||
self.env.flush_all()
|
||
for payslip in payslips:
|
||
number = payslip.number or self.env['ir.sequence'].next_by_code('salary.slip')
|
||
payslip.write({
|
||
'number': number,
|
||
'state': 'verify',
|
||
'compute_date': '2025-01-25'
|
||
})
|
||
self.env['hr.payslip.line'].create(payslips._get_payslip_lines())
|
||
return True
|
||
|
||
# 薪资归档
|
||
def put_release(self):
|
||
for record in self:
|
||
record.write({
|
||
'is_salary_release': True,
|
||
'salary_state': 'to_release',
|
||
})
|
||
|
||
# 薪资归档
|
||
def all_put_release(self):
|
||
payslip_ids = self.env["hr.payslip"].browse(self._context.get('active_ids', self._context.get('active_id')))
|
||
for record in payslip_ids:
|
||
record.write({
|
||
'is_salary_release': True,
|
||
'salary_state': 'to_release',
|
||
})
|
||
|
||
def return_salary_button(self):
|
||
for record in self:
|
||
record.write({'is_salary_release': False,
|
||
'salary_state': 'to_signed'})
|
||
|
||
|
||
def all_return_release(self):
|
||
payslip_ids = self.env["hr.payslip"].browse(self._context.get('active_ids', self._context.get('active_id')))
|
||
for record in payslip_ids:
|
||
record.write({
|
||
'is_salary_release': False,
|
||
'salary_state': 'to_signed',
|
||
})
|
||
|
||
def button_salary_print(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"name": "打印薪资",
|
||
"res_model": "salary.print.wizard",
|
||
"view_mode": "form",
|
||
"target": "new",
|
||
}
|
||
|
||
def open_archive_record(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"res_model": "hr.payslip.line",
|
||
"name": "薪资明细",
|
||
"view_mode": "tree",
|
||
"views": [(False, "tree")],
|
||
"domain": [("slip_id", "=", self.id), ("amount", "!=", 0)],
|
||
"target": "new",
|
||
"context": {
|
||
"tree_view_ref": "yuthon_salary.view_hr_payslip_line_tree_custom",
|
||
"group_by": ["category_id"], # 按 category_id 分组
|
||
},
|
||
}
|
||
|
||
def open_record(self):
|
||
return {
|
||
"type": "ir.actions.act_window",
|
||
"res_model": 'hr.payslip',
|
||
"res_id": self.id,
|
||
"name": "薪资详情",
|
||
"view_mode": "form",
|
||
}
|
||
|
||
def update_record(self):
|
||
payslip_ids = self.env["hr.payslip"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
for record in payslip_ids:
|
||
record.write({
|
||
'salary_group_id': record.employee_id.contract_id.salary_group_id.id,
|
||
})
|
||
|
||
@api.model
|
||
def get_view(self, view_id=None, view_type='form', **options):
|
||
# 检查是否通过特定动作触发
|
||
if options.get('action_id') == 3666:
|
||
# 获取当前用户相关的未确认工资单
|
||
domain = [
|
||
('employee_id.user_id', '=', self.env.uid),
|
||
]
|
||
unconfirmed_slips = self.search(domain)
|
||
if unconfirmed_slips:
|
||
unconfirmed_slips.write({'confirm_status': 'confirm'})
|
||
return super().get_view(view_id, view_type, **options)
|
||
|
||
def initialize_payroll_wizard(self):
|
||
payslip_ids = self.env["hr.payslip"].browse(
|
||
self._context.get('active_ids', self._context.get('active_id')))
|
||
payslip_list = []
|
||
for record in payslip_ids:
|
||
payslip_list.append([4, record.id])
|
||
return {
|
||
'name': '初始化工资单',
|
||
'type': 'ir.actions.act_window',
|
||
'view_mode': 'form',
|
||
'res_model': 'initialize.payroll.wizard',
|
||
'target': 'new',
|
||
'context': {
|
||
'default_payslip_ids': payslip_list
|
||
}
|
||
}
|
||
|