2026-09-25 18:00:58 +05:30

83 lines
4.0 KiB
Python

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}.")