114 lines
4.7 KiB
Python

import datetime
from odoo import http
from odoo.http import request
# Inherit existing StudentPortalController to reuse _get_student()
from odoo.addons.education_portal.controllers.main import StudentPortalController
class TimetablePortalController(StudentPortalController):
def _format_float_time(self, float_time):
"""Convert float time (e.g. 9.5) to formatted string (09:30 AM)"""
hours = int(float_time)
minutes = int(round((float_time - hours) * 60))
if minutes == 60:
minutes = 0
hours += 1
# Handle 24-hour wrap around
hours = hours % 24
am_pm = 'AM' if hours < 12 else 'PM'
display_hours = hours % 12
if display_hours == 0:
display_hours = 12
return f"{display_hours:02d}:{minutes:02d} {am_pm}"
@http.route(['/my/timetable'], type='http', auth="user", website=True)
def student_timetable(self, **kw):
student = self._get_student()
if not student:
return request.redirect('/my/education/dashboard')
# Determine eligibility exactly mapping to student's current academic placement
program_id = student.program_id.id if student.program_id else False
year_id = student.academic_year_id.id if student.academic_year_id else False
term_id = student.academic_term_id.id if student.academic_term_id else False
batch_id = student.batch_id.id if student.batch_id else False
if not program_id or not year_id:
# If a student isn't actively placed in a program/year, they have no timetable.
return request.render("education_timetable.portal_student_timetable", {
'student': student,
'grouped_slots': {},
'page_name': 'timetable',
'error': 'You must be assigned to an Academic Year and Program to view your timetable.'
})
# Build Domain: The portal query must match the student's actual academic placement.
# Respecting optional-field logic (established in Phase C):
# - term_id = False means it applies to all terms.
# - batch_id = False means it applies to all batches.
domain = [
('program_id', '=', program_id),
('academic_year_id', '=', year_id),
('active', '=', True)
]
# We must use sudo() since portal users don't have backend employee groups.
# The domain rigorously isolates records to ONLY those belonging to the student's exact academic profile.
slot_model = request.env['education.timetable.slot'].sudo()
# Handle nullable term_id
if term_id:
domain += ['|', ('term_id', '=', False), ('term_id', '=', term_id)]
else:
domain += [('term_id', '=', False)]
# Handle nullable batch_id
if batch_id:
domain += ['|', ('batch_id', '=', False), ('batch_id', '=', batch_id)]
else:
domain += [('batch_id', '=', False)]
# Optional: Filter by date if provided in query params
filter_date = kw.get('date')
if filter_date:
try:
# Ensure it's a valid date string
parsed_date = datetime.datetime.strptime(filter_date, '%Y-%m-%d').date()
domain += [('date', '=', parsed_date)]
except ValueError:
pass
slots = slot_model.search(domain, order="date asc, start_time asc")
# Group records by Date for clean UI display
grouped_slots = {}
for slot in slots:
date_obj = slot.date
if date_obj not in grouped_slots:
grouped_slots[date_obj] = []
# Format time natively in Python so templates stay clean
formatted_start = self._format_float_time(slot.start_time)
formatted_end = self._format_float_time(slot.end_time)
grouped_slots[date_obj].append({
'id': slot.id,
'start_time': formatted_start,
'end_time': formatted_end,
'subject': slot.subject_id.name,
'faculty': slot.faculty_id.name,
'room': slot.room_id.name,
'batch': slot.batch_id.name if slot.batch_id else 'All Batches',
'term': slot.term_id.name if slot.term_id else 'All Terms',
})
values = {
'student': student,
'grouped_slots': grouped_slots,
'page_name': 'timetable',
}
return request.render("education_timetable.portal_student_timetable", values)