# -*- 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)