192 lines
8.6 KiB
Python

# pyrefly: ignore [missing-import]
from odoo import api, fields, models
# pyrefly: ignore [missing-import]
from odoo.exceptions import ValidationError
class EducationAttendanceSession(models.Model):
_name = 'education.attendance.session'
_description = 'Attendance Session'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'attendance_date desc, period asc, id desc'
name = fields.Char(string='Session Title', required=True, tracking=True)
campus_id = fields.Many2one('education.campus', string='Campus', required=True, index=True)
department_id = fields.Many2one('education.department', string='Department', required=True, index=True)
program_id = fields.Many2one('education.program', string='Program', required=True, index=True)
academic_year_id = fields.Many2one('education.academic.year', string='Academic Year', required=True, index=True)
academic_term_id = fields.Many2one('education.academic.term', string='Academic Term', required=True, index=True)
academic_level_id = fields.Many2one('education.academic.level', string='Academic Level', required=True, index=True)
batch_id = fields.Many2one('education.batch', string='Batch', index=True)
semester_id = fields.Many2one('education.semester', string='Semester', index=True)
section_id = fields.Many2one('education.section', string='Section', index=True)
attendance_date = fields.Date(string='Attendance Date', required=True, default=fields.Date.context_today, index=True, tracking=True)
period = fields.Selection([
('period_1', 'Period 1'),
('period_2', 'Period 2'),
('period_3', 'Period 3'),
('period_4', 'Period 4'),
('period_5', 'Period 5'),
('period_6', 'Period 6'),
('period_7', 'Period 7'),
('period_8', 'Period 8')
], string='Period', required=True, default='period_1', tracking=True)
faculty_id = fields.Many2one('res.users', string='Faculty', required=True, index=True, default=lambda self: self.env.user, tracking=True)
state = fields.Selection([
('draft', 'Draft'),
('open', 'Open'),
('completed', 'Completed'),
('cancelled', 'Cancelled')
], string='Status', default='draft', required=True, index=True, tracking=True)
line_ids = fields.One2many('education.attendance.line', 'session_id', string='Attendance Lines', copy=True)
active = fields.Boolean(default=True)
company_id = fields.Many2one(
'res.company',
string='Company',
required=True,
default=lambda self: self.env.company,
index=True,
)
_uniq_session_period = models.Constraint('unique(attendance_date, period, program_id, academic_level_id, company_id)', 'An attendance session has already been created for this period, program, and academic level on this date.')
@api.constrains('attendance_date')
def _check_attendance_date(self):
today = fields.Date.context_today(self)
for rec in self:
if rec.attendance_date and rec.attendance_date > today:
raise ValidationError("Attendance session date cannot be in the future.")
def write(self, vals):
for rec in self:
if rec.state == 'completed' and not self.env.user.has_group('education_attendance.group_attendance_manager'):
locked_fields = {'campus_id', 'department_id', 'program_id', 'academic_year_id', 'academic_term_id', 'academic_level_id', 'attendance_date', 'period', 'batch_id', 'semester_id', 'section_id'}
if any(f in vals for f in locked_fields):
raise ValidationError("Completed attendance sessions cannot have their schedule or academic grouping modified. Reset to draft or contact an Attendance Manager.")
return super().write(vals)
def unlink(self):
for rec in self:
if rec.state == 'completed' and not self.env.user.has_group('education_attendance.group_attendance_manager'):
raise ValidationError("Completed attendance sessions cannot be deleted directly. Cancel the session first or contact an Attendance Manager.")
return super().unlink()
def action_open(self):
for rec in self:
if rec.state != 'draft':
continue
# Auto-populate attendance lines with active students matching academic context
domain = [
('campus_id', '=', rec.campus_id.id),
('department_id', '=', rec.department_id.id),
('program_id', '=', rec.program_id.id),
('academic_year_id', '=', rec.academic_year_id.id),
('academic_term_id', '=', rec.academic_term_id.id),
('level_id', '=', rec.academic_level_id.id),
('status', '=', 'active'),
]
if rec.batch_id:
domain.append(('batch_id', '=', rec.batch_id.id))
if rec.semester_id:
domain.append(('semester_id', '=', rec.semester_id.id))
if rec.section_id:
domain.append(('section_id', '=', rec.section_id.id))
students = self.env['education.student'].search(domain)
if not students:
raise ValidationError("No active students found matching the selected academic criteria.")
# Create lines
lines_vals = []
for student in students:
lines_vals.append({
'session_id': rec.id,
'student_id': student.id,
'status': 'present',
'company_id': rec.company_id.id,
})
self.env['education.attendance.line'].create(lines_vals)
rec.state = 'open'
def action_complete(self):
for rec in self:
if rec.state != 'open':
continue
if not rec.line_ids:
raise ValidationError("Cannot complete session without any student lines.")
# Copy to permanent history table
history_vals = []
for line in rec.line_ids:
history_vals.append({
'student_id': line.student_id.id,
'date': rec.attendance_date,
'session_id': rec.id,
'status': line.status,
'faculty_id': rec.faculty_id.id,
'remarks': line.remarks,
'company_id': rec.company_id.id,
})
self.env['education.attendance'].create(history_vals)
rec.state = 'completed'
# Invalidate student attendance cache
students = rec.line_ids.mapped('student_id')
students.invalidate_recordset()
def action_cancel(self):
for rec in self:
if rec.state == 'completed':
# Delete permanent history
history = self.env['education.attendance'].search([('session_id', '=', rec.id)])
history.unlink()
# Delete lines
rec.line_ids.unlink()
rec.state = 'cancelled'
def action_draft(self):
for rec in self:
# Delete permanent history if completed
if rec.state == 'completed':
history = self.env['education.attendance'].search([('session_id', '=', rec.id)])
history.unlink()
# Delete lines
rec.line_ids.unlink()
rec.state = 'draft'
# Bulk marking actions
def action_bulk_present(self):
self.ensure_one()
if self.state != 'open':
raise ValidationError("Bulk marking is only allowed when the session is Open.")
self.line_ids.write({'status': 'present'})
def action_bulk_absent(self):
self.ensure_one()
if self.state != 'open':
raise ValidationError("Bulk marking is only allowed when the session is Open.")
self.line_ids.write({'status': 'absent'})
def action_bulk_late(self):
self.ensure_one()
if self.state != 'open':
raise ValidationError("Bulk marking is only allowed when the session is Open.")
self.line_ids.write({'status': 'late'})
def action_bulk_medical(self):
self.ensure_one()
if self.state != 'open':
raise ValidationError("Bulk marking is only allowed when the session is Open.")
self.line_ids.write({'status': 'medical_leave'})
def action_bulk_duty(self):
self.ensure_one()
if self.state != 'open':
raise ValidationError("Bulk marking is only allowed when the session is Open.")
self.line_ids.write({'status': 'on_duty'})