231 lines
8.7 KiB
Python
231 lines
8.7 KiB
Python
from odoo import api, fields, models
|
|
from odoo.exceptions import ValidationError
|
|
|
|
|
|
class DanceSession(models.Model):
|
|
_name = "dance.session"
|
|
_description = "Dance Class Session"
|
|
_order = "date desc, start_time desc, id desc"
|
|
|
|
name = fields.Char(compute="_compute_name", store=True)
|
|
batch_id = fields.Many2one("dance.batch", required=True, ondelete="restrict")
|
|
date = fields.Date(required=True, default=fields.Date.context_today)
|
|
start_time = fields.Float()
|
|
end_time = fields.Float()
|
|
room = fields.Char()
|
|
instructor_id = fields.Many2one("hr.employee", string="Instructor")
|
|
substitute_instructor_id = fields.Many2one(
|
|
"hr.employee",
|
|
string="Substitute Instructor",
|
|
)
|
|
is_cancelled = fields.Boolean(string="Cancelled")
|
|
cancellation_reason = fields.Text()
|
|
makeup_for_id = fields.Many2one(
|
|
"dance.session",
|
|
string="Makeup For",
|
|
domain="[('status', '=', 'cancelled')]",
|
|
ondelete="set null",
|
|
)
|
|
makeup_session_ids = fields.One2many(
|
|
"dance.session",
|
|
"makeup_for_id",
|
|
string="Makeup Sessions",
|
|
)
|
|
status = fields.Selection(
|
|
[
|
|
("scheduled", "Scheduled"),
|
|
("completed", "Completed"),
|
|
("cancelled", "Cancelled"),
|
|
],
|
|
default="scheduled",
|
|
required=True,
|
|
)
|
|
notes = fields.Text()
|
|
|
|
attendance_ids = fields.One2many(
|
|
"dance.attendance",
|
|
"session_id",
|
|
string="Attendance",
|
|
)
|
|
attendance_count = fields.Integer(compute="_compute_attendance_counts")
|
|
present_count = fields.Integer(compute="_compute_attendance_counts")
|
|
absent_count = fields.Integer(compute="_compute_attendance_counts")
|
|
late_count = fields.Integer(compute="_compute_attendance_counts")
|
|
|
|
@api.depends("batch_id", "date", "start_time")
|
|
def _compute_name(self):
|
|
for session in self:
|
|
if session.batch_id and session.date:
|
|
session.name = "%s - %s" % (session.batch_id.name, session.date)
|
|
else:
|
|
session.name = "New Session"
|
|
|
|
@api.depends("attendance_ids.status")
|
|
def _compute_attendance_counts(self):
|
|
for session in self:
|
|
session.attendance_count = len(session.attendance_ids)
|
|
session.present_count = len(
|
|
session.attendance_ids.filtered(lambda line: line.status == "present")
|
|
)
|
|
session.absent_count = len(
|
|
session.attendance_ids.filtered(lambda line: line.status == "absent")
|
|
)
|
|
session.late_count = len(
|
|
session.attendance_ids.filtered(lambda line: line.status == "late")
|
|
)
|
|
|
|
@api.onchange("batch_id")
|
|
def _onchange_batch_id(self):
|
|
for session in self:
|
|
if session.batch_id:
|
|
session.instructor_id = session.batch_id.instructor_id
|
|
session.start_time = session.batch_id.start_time
|
|
session.end_time = session.batch_id.end_time
|
|
session.room = session.batch_id.room
|
|
|
|
@api.constrains("batch_id", "date")
|
|
def _check_date_inside_batch(self):
|
|
for session in self:
|
|
batch = session.batch_id
|
|
if not batch or not session.date:
|
|
continue
|
|
if batch.start_date and session.date < batch.start_date:
|
|
raise ValidationError("Session date cannot be before batch start date.")
|
|
if batch.end_date and session.date > batch.end_date:
|
|
raise ValidationError("Session date cannot be after batch end date.")
|
|
|
|
@api.constrains("start_time", "end_time")
|
|
def _check_times(self):
|
|
for session in self:
|
|
if session.end_time and session.start_time and session.end_time <= session.start_time:
|
|
raise ValidationError("End time must be after start time.")
|
|
|
|
@api.constrains("date", "start_time", "end_time", "room", "instructor_id", "substitute_instructor_id", "status")
|
|
def _check_room_and_instructor_conflicts(self):
|
|
for session in self:
|
|
if (
|
|
session.status == "cancelled"
|
|
or not session.date
|
|
or not session.start_time
|
|
or not session.end_time
|
|
):
|
|
continue
|
|
|
|
overlap_domain = [
|
|
("id", "!=", session.id),
|
|
("date", "=", session.date),
|
|
("status", "!=", "cancelled"),
|
|
("start_time", "<", session.end_time),
|
|
("end_time", ">", session.start_time),
|
|
]
|
|
if session.room:
|
|
room_conflict = self.search(overlap_domain + [("room", "=", session.room)], limit=1)
|
|
if room_conflict:
|
|
raise ValidationError(
|
|
"Room conflict: %s is already booked for %s from %.2f to %.2f."
|
|
% (
|
|
session.room,
|
|
room_conflict.name,
|
|
room_conflict.start_time,
|
|
room_conflict.end_time,
|
|
)
|
|
)
|
|
teaching_instructor = session.substitute_instructor_id or session.instructor_id
|
|
if teaching_instructor:
|
|
instructor_conflict = self.search(
|
|
overlap_domain
|
|
+ [
|
|
"|",
|
|
("instructor_id", "=", teaching_instructor.id),
|
|
("substitute_instructor_id", "=", teaching_instructor.id),
|
|
],
|
|
limit=1,
|
|
)
|
|
if instructor_conflict:
|
|
raise ValidationError(
|
|
"Instructor conflict: %s already has %s from %.2f to %.2f."
|
|
% (
|
|
teaching_instructor.name,
|
|
instructor_conflict.name,
|
|
instructor_conflict.start_time,
|
|
instructor_conflict.end_time,
|
|
)
|
|
)
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
for vals in vals_list:
|
|
if vals.get("batch_id"):
|
|
batch = self.env["dance.batch"].browse(vals["batch_id"])
|
|
vals.setdefault("instructor_id", batch.instructor_id.id)
|
|
vals.setdefault("start_time", batch.start_time)
|
|
vals.setdefault("end_time", batch.end_time)
|
|
vals.setdefault("room", batch.room)
|
|
return super().create(vals_list)
|
|
|
|
def write(self, vals):
|
|
if vals.get("is_cancelled"):
|
|
vals = dict(vals, status="cancelled")
|
|
elif vals.get("status") == "cancelled":
|
|
vals = dict(vals, is_cancelled=True)
|
|
return super().write(vals)
|
|
|
|
def action_load_students(self):
|
|
for session in self:
|
|
active_enrollments = self.env["dance.enrollment"].search(
|
|
[
|
|
("batch_id", "=", session.batch_id.id),
|
|
("status", "=", "active"),
|
|
]
|
|
)
|
|
existing_student_ids = set(session.attendance_ids.mapped("student_id").ids)
|
|
attendance_vals = []
|
|
for enrollment in active_enrollments:
|
|
if enrollment.student_id.id in existing_student_ids:
|
|
continue
|
|
attendance_vals.append(
|
|
{
|
|
"session_id": session.id,
|
|
"student_id": enrollment.student_id.id,
|
|
"status": "present",
|
|
}
|
|
)
|
|
if attendance_vals:
|
|
self.env["dance.attendance"].create(attendance_vals)
|
|
return True
|
|
|
|
def action_mark_all_present(self):
|
|
for session in self:
|
|
if not session.attendance_ids:
|
|
session.action_load_students()
|
|
session.attendance_ids.write({"status": "present"})
|
|
return True
|
|
|
|
def action_complete(self):
|
|
self.write({"status": "completed"})
|
|
return True
|
|
|
|
def action_cancel(self):
|
|
self.write({"status": "cancelled", "is_cancelled": True})
|
|
return True
|
|
|
|
def action_create_makeup_session(self):
|
|
self.ensure_one()
|
|
makeup = self.copy(
|
|
{
|
|
"date": fields.Date.context_today(self),
|
|
"status": "scheduled",
|
|
"is_cancelled": False,
|
|
"cancellation_reason": False,
|
|
"makeup_for_id": self.id,
|
|
"attendance_ids": False,
|
|
}
|
|
)
|
|
return {
|
|
"name": "Makeup Session",
|
|
"type": "ir.actions.act_window",
|
|
"res_model": "dance.session",
|
|
"res_id": makeup.id,
|
|
"view_mode": "form",
|
|
}
|