from odoo import _, api, fields, models from odoo.exceptions import ValidationError WEEKDAYS = [ ("mon", "Monday"), ("tue", "Tuesday"), ("wed", "Wednesday"), ("thu", "Thursday"), ("fri", "Friday"), ("sat", "Saturday"), ] class McTimetableSlot(models.Model): _name = "mc.timetable.slot" _description = "Timetable Slot" _order = "year_id desc, weekday, period" _rec_name = "display_name" batch_id = fields.Many2one("mc.batch", string="Batch", required=True, ondelete="cascade") weekday = fields.Selection(WEEKDAYS, string="Weekday", required=True) period = fields.Integer(string="Period", required=True) subject_id = fields.Many2one("mc.subject", string="Subject", required=True, ondelete="restrict") teacher_id = fields.Many2one("hr.employee", string="Teacher", ondelete="restrict") room_id = fields.Many2one("mc.room", string="Room", ondelete="restrict") year_id = fields.Many2one("mc.academic.year", string="Academic Year", required=True, ondelete="restrict") term_id = fields.Many2one("mc.academic.term", string="Term", ondelete="restrict") @api.depends("batch_id.name", "weekday", "period", "subject_id.name") def _compute_display_name(self): weekday_labels = dict(self._fields["weekday"].selection) for slot in self: slot.display_name = "%s - %s P%s - %s" % ( slot.batch_id.name or "?", weekday_labels.get(slot.weekday, "?"), slot.period, slot.subject_id.name or "?", ) def init(self): # A teacher or a room cannot hold two slots in the same # weekday+period (O5 spec, verbatim). Partial unique indexes are # the actual guarantee; _check_no_conflict below only exists to # turn the same violation into a message a scheduler can read, # and - same lesson as mc.enrollment - has to run BEFORE the # insert/update, because the index fires first otherwise. self.env.cr.execute( "CREATE UNIQUE INDEX IF NOT EXISTS mc_timetable_slot_teacher_no_clash " "ON mc_timetable_slot (teacher_id, weekday, period, year_id) " "WHERE teacher_id IS NOT NULL" ) self.env.cr.execute( "CREATE UNIQUE INDEX IF NOT EXISTS mc_timetable_slot_room_no_clash " "ON mc_timetable_slot (room_id, weekday, period, year_id) " "WHERE room_id IS NOT NULL" ) self.env.cr.execute( "CREATE UNIQUE INDEX IF NOT EXISTS mc_timetable_slot_batch_no_clash " "ON mc_timetable_slot (batch_id, weekday, period, year_id)" ) def _check_no_conflict(self, vals, exclude_id=None): weekday = vals.get("weekday") period = vals.get("period") year_id = vals.get("year_id") if not (weekday and period and year_id): return base_domain = [ ("weekday", "=", weekday), ("period", "=", period), ("year_id", "=", year_id), ] if exclude_id: base_domain.append(("id", "!=", exclude_id)) batch_id = vals.get("batch_id") if batch_id and self.search_count(base_domain + [("batch_id", "=", batch_id)]): raise ValidationError(_( "This batch already has a slot on this weekday and period." )) teacher_id = vals.get("teacher_id") if teacher_id and self.search_count(base_domain + [("teacher_id", "=", teacher_id)]): raise ValidationError(_( "This teacher already has a slot on this weekday and period." )) room_id = vals.get("room_id") if room_id and self.search_count(base_domain + [("room_id", "=", room_id)]): raise ValidationError(_( "This room is already booked for this weekday and period." )) @api.model_create_multi def create(self, vals_list): for vals in vals_list: self._check_no_conflict(vals) return super().create(vals_list) def write(self, vals): if any(key in vals for key in ("weekday", "period", "year_id", "batch_id", "teacher_id", "room_id")): for slot in self: merged = { "weekday": vals.get("weekday", slot.weekday), "period": vals.get("period", slot.period), "year_id": vals.get("year_id", slot.year_id.id), "batch_id": vals.get("batch_id", slot.batch_id.id), "teacher_id": vals.get("teacher_id", slot.teacher_id.id), "room_id": vals.get("room_id", slot.room_id.id), } self._check_no_conflict(merged, exclude_id=slot.id) return super().write(vals)