shcool/yuthon_attendance/models/attendance.py
2026-07-31 18:15:34 +08:00

115 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
from odoo import fields, models, api
from datetime import timedelta
# 教学用:规定上班时间为 09:00。
# 注意Odoo 数据库里的 Datetime 以 UTC 存储,这里直接用字段的“小时”做演示比较;
# 生产环境应先用 fields.Datetime.context_timestamp() 把时间转换到用户时区再判断。
WORK_START_HOUR = 9
class Attendance(models.Model):
"""教职工出勤记录。"""
_name = 'yuthon.attendance'
_description = '出勤记录'
_order = 'date desc, employee_id'
employee_id = fields.Many2one(
'yuthon.employee',
string='教职工',
required=True,
ondelete='cascade',
)
date = fields.Date(
string='日期',
required=True,
default=fields.Date.today,
)
check_in = fields.Datetime(string='签到时间')
check_out = fields.Datetime(string='签退时间')
# ---- 计算字段 ----
working_hours = fields.Float(
string='工时(小时)',
compute='_compute_working_hours',
store=True,
)
status = fields.Selection(
[
('normal', '正常'),
('late', '迟到'),
('absent', '缺勤'),
],
string='状态',
compute='_compute_status',
store=True,
)
late_minutes = fields.Integer(
string='迟到分钟',
compute='_compute_status',
store=True,
)
# ---- 约束 ----
_sql_constraints = [
('uniq_employee_date', 'unique(employee_id, date)',
'同一教职工一天只能有一条出勤记录!'),
]
@api.constrains('check_in', 'check_out')
def _check_time_order(self):
for rec in self:
if rec.check_in and rec.check_out and rec.check_out < rec.check_in:
raise models.ValidationError('签退时间不能早于签到时间。')
@api.depends('check_in', 'check_out')
def _compute_working_hours(self):
for rec in self:
if rec.check_in and rec.check_out and rec.check_out >= rec.check_in:
delta = rec.check_out - rec.check_in
rec.working_hours = round(delta.total_seconds() / 3600.0, 2)
else:
rec.working_hours = 0.0
@api.depends('check_in')
def _compute_status(self):
for rec in self:
if not rec.check_in:
rec.status = 'absent'
rec.late_minutes = 0
continue
check_in_hour = rec.check_in.hour + rec.check_in.minute / 60.0
if check_in_hour > WORK_START_HOUR:
rec.status = 'late'
threshold = rec.check_in.replace(
hour=WORK_START_HOUR, minute=0, second=0, microsecond=0)
rec.late_minutes = max(0, int(
(rec.check_in - threshold).total_seconds() // 60))
else:
rec.status = 'normal'
rec.late_minutes = 0
class Employee(models.Model):
"""通过 _inherit 反向扩展 yuthon.employee把出勤记录挂上去。
这是「模块组合」的关键示范yuthon_attendance 不修改 yuthon_employee 的源码,
而是继承它追加 One2many 反向关联,让两个模块保持解耦。
"""
_inherit = 'yuthon.employee'
attendance_ids = fields.One2many(
'yuthon.attendance',
'employee_id',
string='出勤记录',
)
attendance_count = fields.Integer(
string='出勤记录数',
compute='_compute_attendance_count',
)
@api.depends('attendance_ids')
def _compute_attendance_count(self):
for rec in self:
rec.attendance_count = len(rec.attendance_ids)