202 lines
9.3 KiB
Python
202 lines
9.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import ValidationError
|
|
|
|
|
|
class SalarySlip(models.Model):
|
|
"""薪资单 - 教职工月度薪资发放记录"""
|
|
_name = 'yuthon.salary.slip'
|
|
_description = '薪资单'
|
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
|
_order = 'year desc, month desc, employee_id'
|
|
|
|
name = fields.Char(string='薪资单编号', required=True, copy=False, readonly=True, default='新建')
|
|
employee_id = fields.Many2one('hr.employee', string='教职工', required=True)
|
|
department_id = fields.Many2one('hr.department', string='所属部门', related='employee_id.department_id', store=True)
|
|
job_id = fields.Many2one('hr.job', string='岗位', related='employee_id.job_id', store=True)
|
|
|
|
# 薪资周期
|
|
year = fields.Integer(string='年度', required=True, default=lambda self: fields.Date.today().year)
|
|
month = fields.Integer(string='月份', required=True, default=lambda self: fields.Date.today().month)
|
|
date_from = fields.Date(string='开始日期', required=True)
|
|
date_to = fields.Date(string='结束日期', required=True)
|
|
|
|
# 关联档案
|
|
salary_record_id = fields.Many2one('yuthon.employee.salary', string='薪资档案')
|
|
|
|
# 收入项
|
|
basic_salary = fields.Float(string='基本工资', default=0.0)
|
|
position_salary = fields.Float(string='岗位工资', default=0.0)
|
|
performance_salary = fields.Float(string='绩效工资', default=0.0)
|
|
class_hour_fee = fields.Float(string='课时费', default=0.0)
|
|
class_teacher_allowance = fields.Float(string='班主任津贴', default=0.0)
|
|
seniority_allowance = fields.Float(string='工龄津贴', default=0.0)
|
|
education_allowance = fields.Float(string='学历津贴', default=0.0)
|
|
title_allowance = fields.Float(string='职称津贴', default=0.0)
|
|
other_income = fields.Float(string='其他收入', default=0.0)
|
|
|
|
# 扣款项
|
|
pension_personal = fields.Float(string='养老保险(个人)', default=0.0)
|
|
medical_personal = fields.Float(string='医疗保险(个人)', default=0.0)
|
|
unemployment_personal = fields.Float(string='失业保险(个人)', default=0.0)
|
|
provident_fund_personal = fields.Float(string='公积金(个人)', default=0.0)
|
|
other_deduction = fields.Float(string='其他扣款', default=0.0)
|
|
|
|
# 个税
|
|
taxable_income = fields.Float(string='应纳税所得额', compute='_compute_totals', store=True)
|
|
personal_income_tax = fields.Float(string='个人所得税', compute='_compute_tax', store=True)
|
|
|
|
# 合计
|
|
total_income = fields.Float(string='应发合计', compute='_compute_totals', store=True)
|
|
total_deduction = fields.Float(string='扣款合计', compute='_compute_totals', store=True)
|
|
net_salary = fields.Float(string='实发工资', compute='_compute_totals', store=True)
|
|
|
|
# 银行信息
|
|
bank_name = fields.Char(string='开户银行')
|
|
bank_account = fields.Char(string='银行卡号')
|
|
|
|
# 状态
|
|
state = fields.Selection([
|
|
('draft', '草稿'),
|
|
('confirmed', '已确认'),
|
|
('approved', '已审核'),
|
|
('paid', '已发放'),
|
|
], string='状态', default='draft', tracking=True)
|
|
|
|
line_ids = fields.One2many('yuthon.salary.slip.line', 'slip_id', string='薪资明细')
|
|
note = fields.Text(string='备注')
|
|
company_id = fields.Many2one('res.company', string='学校', default=lambda self: self.env.company)
|
|
|
|
@api.depends('basic_salary', 'position_salary', 'performance_salary', 'class_hour_fee',
|
|
'class_teacher_allowance', 'seniority_allowance', 'education_allowance',
|
|
'title_allowance', 'other_income',
|
|
'pension_personal', 'medical_personal', 'unemployment_personal',
|
|
'provident_fund_personal', 'other_deduction', 'personal_income_tax')
|
|
def _compute_totals(self):
|
|
for slip in self:
|
|
income = (
|
|
slip.basic_salary + slip.position_salary + slip.performance_salary +
|
|
slip.class_hour_fee + slip.class_teacher_allowance +
|
|
slip.seniority_allowance + slip.education_allowance +
|
|
slip.title_allowance + slip.other_income
|
|
)
|
|
deduction = (
|
|
slip.pension_personal + slip.medical_personal +
|
|
slip.unemployment_personal + slip.provident_fund_personal +
|
|
slip.other_deduction + slip.personal_income_tax
|
|
)
|
|
slip.total_income = income
|
|
slip.total_deduction = deduction
|
|
slip.taxable_income = max(0, income - deduction + slip.personal_income_tax - 5000)
|
|
slip.net_salary = income - deduction
|
|
|
|
@api.depends('taxable_income')
|
|
def _compute_tax(self):
|
|
"""简化版个税计算(中国个税累进税率)"""
|
|
for slip in self:
|
|
taxable = slip.taxable_income
|
|
if taxable <= 0:
|
|
slip.personal_income_tax = 0
|
|
elif taxable <= 3000:
|
|
slip.personal_income_tax = taxable * 0.03
|
|
elif taxable <= 12000:
|
|
slip.personal_income_tax = taxable * 0.10 - 210
|
|
elif taxable <= 25000:
|
|
slip.personal_income_tax = taxable * 0.20 - 1410
|
|
elif taxable <= 35000:
|
|
slip.personal_income_tax = taxable * 0.25 - 2660
|
|
elif taxable <= 55000:
|
|
slip.personal_income_tax = taxable * 0.30 - 4410
|
|
elif taxable <= 80000:
|
|
slip.personal_income_tax = taxable * 0.35 - 7160
|
|
else:
|
|
slip.personal_income_tax = taxable * 0.45 - 15160
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
for vals in vals_list:
|
|
if vals.get('name', '新建') == '新建':
|
|
vals['name'] = self.env['ir.sequence'].next_by_code('yuthon.salary.slip') or '新建'
|
|
return super(SalarySlip, self).create(vals_list)
|
|
|
|
def action_calculate(self):
|
|
"""自动计算薪资"""
|
|
for slip in self:
|
|
if slip.salary_record_id:
|
|
record = slip.salary_record_id
|
|
slip.basic_salary = record.basic_salary
|
|
slip.position_salary = record.position_salary
|
|
slip.performance_salary = record.performance_salary
|
|
slip.seniority_allowance = record.seniority_allowance
|
|
slip.education_allowance = record.education_allowance
|
|
slip.title_allowance = record.title_allowance
|
|
slip.bank_name = record.bank_name
|
|
slip.bank_account = record.bank_account
|
|
|
|
# 计算社保
|
|
if record.social_insurance_base > 0:
|
|
slip.pension_personal = record.social_insurance_base * record.pension_rate_personal / 100
|
|
slip.medical_personal = record.social_insurance_base * record.medical_rate_personal / 100
|
|
slip.unemployment_personal = record.social_insurance_base * record.unemployment_rate_personal / 100
|
|
if record.provident_fund_base > 0:
|
|
slip.provident_fund_personal = record.provident_fund_base * record.provident_fund_rate_personal / 100
|
|
|
|
# 统计课时费
|
|
class_hours = self.env['yuthon.class.hour'].search([
|
|
('employee_id', '=', slip.employee_id.id),
|
|
('date', '>=', slip.date_from),
|
|
('date', '<=', slip.date_to),
|
|
('state', 'in', ['approved', 'paid']),
|
|
])
|
|
slip.class_hour_fee = sum(ch.amount for ch in class_hours)
|
|
|
|
# 统计班主任津贴
|
|
allowances = self.env['yuthon.class.teacher.allowance'].search([
|
|
('employee_id', '=', slip.employee_id.id),
|
|
('year', '=', slip.year),
|
|
('month', '=', slip.month),
|
|
('state', 'in', ['approved', 'paid']),
|
|
])
|
|
slip.class_teacher_allowance = sum(a.total_amount for a in allowances)
|
|
|
|
slip._compute_totals()
|
|
slip._compute_tax()
|
|
|
|
def action_confirm(self):
|
|
self.write({'state': 'confirmed'})
|
|
|
|
def action_approve(self):
|
|
self.write({'state': 'approved'})
|
|
|
|
def action_pay(self):
|
|
self.write({'state': 'paid'})
|
|
|
|
def action_draft(self):
|
|
self.write({'state': 'draft'})
|
|
|
|
@api.constrains('year', 'month')
|
|
def _check_year_month(self):
|
|
for slip in self:
|
|
if slip.month < 1 or slip.month > 12:
|
|
raise ValidationError(_('月份必须在 1-12 之间!'))
|
|
if slip.year < 2000 or slip.year > 2100:
|
|
raise ValidationError(_('年度格式不正确!'))
|
|
|
|
|
|
class SalarySlipLine(models.Model):
|
|
"""薪资单明细行"""
|
|
_name = 'yuthon.salary.slip.line'
|
|
_description = '薪资单明细'
|
|
_order = 'sequence, id'
|
|
|
|
slip_id = fields.Many2one('yuthon.salary.slip', string='薪资单', required=True, ondelete='cascade')
|
|
sequence = fields.Integer(string='排序', default=10)
|
|
item_id = fields.Many2one('yuthon.salary.item', string='薪资项目')
|
|
name = fields.Char(string='项目名称')
|
|
item_type = fields.Selection([
|
|
('income', '收入'),
|
|
('deduction', '扣款'),
|
|
], string='类型')
|
|
amount = fields.Float(string='金额', default=0.0)
|
|
note = fields.Char(string='备注')
|