shcool/yuthon_wage/models/payslip.py
2026-08-03 09:57:51 +08:00

107 lines
3.9 KiB
Python

# -*- coding: utf-8 -*-
from odoo import fields, models, api
# 教学用扣款规则(可在 production 改为可配置的 salary.rule 模型)
LATE_PENALTY = 50.0 # 迟到一次扣 50
DAYS_PER_MONTH = 21.75 # 月计薪天数,用于把“缺勤天数”折算成金额
class Payslip(models.Model):
"""工资条:聚合员工当月出勤,自动计算扣款与实发。"""
_name = 'yuthon.payslip'
_description = '工资条'
_order = 'period_end desc, employee_id'
employee_id = fields.Many2one(
'yuthon.employee',
string='教职工',
required=True,
ondelete='cascade',
)
period_start = fields.Date(string='起始日期', required=True)
period_end = fields.Date(string='结束日期', required=True)
# ---- 金额 ----
basic_wage = fields.Float(
string='基本工资',
related='employee_id.base_salary',
store=True,
readonly=True,
)
allowance = fields.Float(string='津贴/补贴', default=0.0)
# ---- 出勤聚合(自动统计)----
late_count = fields.Integer(
string='迟到次数', compute='_compute_attendance_stats', store=True)
absent_days = fields.Integer(
string='缺勤天数', compute='_compute_attendance_stats', store=True)
# ---- 扣款(自动计算)----
late_deduction = fields.Float(
string='迟到扣款', compute='_compute_deductions', store=True)
absent_deduction = fields.Float(
string='缺勤扣款', compute='_compute_deductions', store=True)
attendance_deduction = fields.Float(
string='出勤扣款合计', compute='_compute_deductions', store=True)
# ---- 实发 ----
net_salary = fields.Float(
string='实发工资', compute='_compute_net', store=True)
state = fields.Selection(
[('draft', '草稿'), ('done', '已确认')],
string='状态',
default='draft',
)
# ---- 计算逻辑 ----
@api.depends('employee_id', 'period_start', 'period_end')
def _compute_attendance_stats(self):
Attendance = self.env['yuthon.attendance']
for rec in self:
domain = [('employee_id', '=', rec.employee_id.id)]
if rec.period_start:
domain.append(('date', '>=', rec.period_start))
if rec.period_end:
domain.append(('date', '<=', rec.period_end))
# sudo() 确保在后台重算时不受当前用户 ACL 影响
rec.late_count = Attendance.sudo().search_count(
domain + [('status', '=', 'late')])
rec.absent_days = Attendance.sudo().search_count(
domain + [('status', '=', 'absent')])
@api.depends('late_count', 'absent_days', 'basic_wage')
def _compute_deductions(self):
for rec in self:
rec.late_deduction = rec.late_count * LATE_PENALTY
daily = (rec.basic_wage / DAYS_PER_MONTH) if rec.basic_wage else 0.0
rec.absent_deduction = rec.absent_days * daily
rec.attendance_deduction = rec.late_deduction + rec.absent_deduction
@api.depends('basic_wage', 'allowance', 'attendance_deduction')
def _compute_net(self):
for rec in self:
rec.net_salary = rec.basic_wage + rec.allowance - rec.attendance_deduction
# ---- 业务动作 ----
def action_confirm(self):
self.write({'state': 'done'})
def action_draft(self):
self.write({'state': 'draft'})
class Employee(models.Model):
"""_inherit 反向扩展:把工资条挂到员工上。"""
_inherit = 'yuthon.employee'
payslip_ids = fields.One2many(
'yuthon.payslip', 'employee_id', string='工资条')
payslip_count = fields.Integer(
string='工资条数', compute='_compute_payslip_count')
@api.depends('payslip_ids')
def _compute_payslip_count(self):
for rec in self:
rec.payslip_count = len(rec.payslip_ids)