from odoo import models, fields, api from odoo.exceptions import ValidationError class EducationTimetableSlot(models.Model): _name = 'education.timetable.slot' _description = 'Timetable Slot' _order = 'date desc, start_time' # Academic References (Student Population) program_id = fields.Many2one('education.program', string='Program', required=True) academic_year_id = fields.Many2one('education.academic.year', string='Academic Year', required=True) term_id = fields.Many2one('education.academic.term', string='Term') batch_id = fields.Many2one('education.batch', string='Batch') # Slot Details subject_id = fields.Many2one('education.subject', string='Subject', required=True) faculty_id = fields.Many2one('res.users', string='Faculty / Instructor', required=True) room_id = fields.Many2one('education.room', string='Room / Venue', required=True) # Schedule date = fields.Date(string='Date', required=True) start_time = fields.Float(string='Start Time', required=True) end_time = fields.Float(string='End Time', required=True) active = fields.Boolean(default=True) state = fields.Selection([ ('draft', 'Draft'), ('confirmed', 'Confirmed'), ('cancelled', 'Cancelled') ], string='Status', default='draft') @api.constrains('start_time', 'end_time') def _check_time(self): for rec in self: if rec.start_time >= rec.end_time: raise ValidationError("Start time must be strictly earlier than end time.") @api.constrains('date', 'start_time', 'end_time', 'faculty_id', 'room_id', 'program_id', 'academic_year_id', 'term_id', 'batch_id') def _check_overlap(self): for rec in self: domain_base = [ ('date', '=', rec.date), ('id', '!=', rec.id), # Interval logic: existing_start < new_end AND existing_end > new_start ('start_time', '<', rec.end_time), ('end_time', '>', rec.start_time), ] # 1. Check Faculty Conflict faculty_conflict = self.search(domain_base + [('faculty_id', '=', rec.faculty_id.id)], limit=1) if faculty_conflict: raise ValidationError(f"Faculty conflict: {rec.faculty_id.name} already has a class overlapping this time on {rec.date}.") # 2. Check Room Conflict room_conflict = self.search(domain_base + [('room_id', '=', rec.room_id.id)], limit=1) if room_conflict: raise ValidationError(f"Room conflict: {rec.room_id.name} is already booked for an overlapping time on {rec.date}.") # 3. Check Student Population Conflict # Population is defined by Program + Academic Year + Term + Batch # If a slot has NO term, it applies to the whole year, so it should conflict with ANY term in that year. # So if either the new slot or the existing slot has no term (or they match), it's a conflict. population_domain = domain_base + [ ('program_id', '=', rec.program_id.id), ('academic_year_id', '=', rec.academic_year_id.id), ] # OR logic for term_id: either matches exactly, or one of them is False if rec.term_id: population_domain.append('|') population_domain.append(('term_id', '=', False)) population_domain.append(('term_id', '=', rec.term_id.id)) # OR logic for batch_id: either matches exactly, or one of them is False if rec.batch_id: population_domain.append('|') population_domain.append(('batch_id', '=', False)) population_domain.append(('batch_id', '=', rec.batch_id.id)) population_conflict = self.search(population_domain, limit=1) if population_conflict: raise ValidationError(f"Student Population conflict: This specific academic group already has a class overlapping this time on {rec.date}.")