129 lines
5.2 KiB
Python
129 lines
5.2 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
|
|
|
|
semester_id = student.semester_id.id if getattr(student, 'semester_id', False) else False
|
|
section_id = student.section_id.id if getattr(student, 'section_id', False) 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.
|
|
domain = [
|
|
('program_id', '=', program_id),
|
|
('academic_year_id', '=', year_id),
|
|
('active', '=', True),
|
|
('state', '!=', 'cancelled'),
|
|
]
|
|
if student.company_id:
|
|
domain.append(('company_id', '=', student.company_id.id))
|
|
|
|
# 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)]
|
|
|
|
# Handle nullable semester_id
|
|
if semester_id:
|
|
domain += ['|', ('semester_id', '=', False), ('semester_id', '=', semester_id)]
|
|
else:
|
|
domain += [('semester_id', '=', False)]
|
|
|
|
# Handle nullable section_id
|
|
if section_id:
|
|
domain += ['|', ('section_id', '=', False), ('section_id', '=', section_id)]
|
|
else:
|
|
domain += [('section_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)
|