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