shcool/yuthon_salary/models/salary_slip.py
2026-07-30 19:56:44 +08:00

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='备注')