import base64 # pyrefly: ignore [missing-import] from odoo import http # pyrefly: ignore [missing-import] from odoo.http import request # pyrefly: ignore [missing-import] from odoo.addons.portal.controllers.portal import CustomerPortal class StudentPortalController(CustomerPortal): def _get_student(self): return request.env.user.student_id def _prepare_home_portal_values(self, counters): values = super()._prepare_home_portal_values(counters) student = self._get_student() if student: values['student_portal_count'] = 1 return values @http.route(['/my', '/my/home'], type='http', auth="user", website=True) def home(self, **kw): student = self._get_student() if student: return request.redirect('/my/education/dashboard') return super().home(**kw) @http.route(['/my/education/dashboard'], type='http', auth="user", website=True) def student_dashboard(self, **kw): student = self._get_student() if not student: return request.render("education_portal.portal_no_student") # Fetch notices, circulars, events announcements = request.env['education.announcement'].search([('active', '=', True)], limit=5) # Fetch upcoming/ongoing exams for the student's program and academic year exams = request.env['education.exam'].sudo().search([ ('state', 'in', ['scheduled', 'ongoing']), ('program_id', '=', student.program_id.id), ('academic_year_id', '=', student.academic_year_id.id), ], order='start_date asc', limit=5) values = { 'student': student, 'announcements': announcements, 'exams': exams, 'page_name': 'student_dashboard', } return request.render("education_portal.portal_student_dashboard", values) @http.route(['/my/education/profile'], type='http', auth="user", website=True) def student_profile(self, **kw): student = self._get_student() if not student: return request.redirect('/my/education/dashboard') values = { 'student': student, 'page_name': 'student_profile', 'success': kw.get('success'), } return request.render("education_portal.portal_student_profile", values) @http.route(['/my/education/profile/update'], type='http', auth="user", methods=['POST'], website=True, csrf=True) def student_profile_update(self, **post): student = self._get_student() if not student: return request.redirect('/my/education/dashboard') update_vals = {} # Editable contact fields if 'street' in post: update_vals['street'] = post.get('street') if 'street2' in post: update_vals['street2'] = post.get('street2') if 'city' in post: update_vals['city'] = post.get('city') if 'zip' in post: update_vals['zip'] = post.get('zip') if 'guardian_phone' in post: update_vals['guardian_phone'] = post.get('guardian_phone') if 'guardian_email' in post: update_vals['guardian_email'] = post.get('guardian_email') if update_vals: student.sudo().write(update_vals) return request.redirect('/my/education/profile?success=1') @http.route(['/my/education/finance'], type='http', auth="user", website=True) def student_finance(self, **kw): student = self._get_student() if not student: return request.redirect('/my/education/dashboard') ledger_lines = student.env['education.student.ledger'].search([('student_id', '=', student.id)], order='date asc, id asc') payments = student.env['education.student.payment'].search([('student_id', '=', student.id), ('status', '=', 'confirmed')], order='payment_date desc, id desc') if student.outstanding_amount <= 0: unpaid_assignments = student.env['education.fee.assignment'].search([('student_id', '=', student.id), ('payment_status', '!=', 'paid')]) if unpaid_assignments: unpaid_assignments.sudo().write({'payment_status': 'paid'}) pending_fee_assignments = student.env['education.fee.assignment'] else: pending_fee_assignments = student.env['education.fee.assignment'].search([('student_id', '=', student.id), ('payment_status', '!=', 'paid')], order='assignment_date desc') values = { 'student': student, 'ledger_lines': ledger_lines, 'payments': payments, 'pending_fee_assignments': pending_fee_assignments, 'page_name': 'student_finance', 'success_payment': kw.get('success_payment'), } return request.render("education_portal.portal_student_finance", values) @http.route(['/my/education/finance/pay'], type='http', auth="user", website=True) def student_pay(self, **kw): student = self._get_student() if not student: return request.redirect('/my/education/dashboard') values = { 'student': student, 'page_name': 'student_pay', 'error': kw.get('error'), } return request.render("education_portal.portal_student_pay", values) @http.route(['/my/education/finance/pay/confirm'], type='http', auth="user", methods=['POST'], website=True) def student_pay_confirm(self, **post): student = self._get_student() if not student: return request.redirect('/my/education/dashboard') try: amount = float(post.get('amount', 0.0)) except ValueError: amount = 0.0 payment_method = post.get('payment_method', 'online') if amount <= 0 or amount > student.outstanding_amount: return request.redirect('/my/education/finance/pay?error=1') import time # Create payment record payment = student.env['education.student.payment'].sudo().create({ 'student_id': student.id, 'amount': amount, 'payment_method': payment_method, 'receipt_number': f"REC-ONLINE-{int(time.time())}-{student.id}", 'reference_number': post.get('reference_number', 'ONLINE-PORTAL'), 'remarks': 'Payment submitted via Student Self-Service Portal.', 'company_id': student.company_id.id, }) payment.action_confirm() try: return request.redirect('/my/education/finance?success_payment=1') except RuntimeError: # pyrefly: ignore [missing-import] from odoo.http import Response return Response("Success") @http.route(['/my/education/finance/receipt/'], type='http', auth="user", website=True) def download_receipt(self, payment_id, **kw): student = self._get_student() if not student: return request.redirect('/my/education/dashboard') payment = student.env['education.student.payment'].sudo().browse(payment_id) if not payment.exists() or payment.student_id.id != student.id: return request.redirect('/my/education/finance') pdf, _ = student.env['ir.actions.report'].sudo()._render_qweb_pdf('education_finance.action_report_receipt_pdf', [payment.id]) pdfhttpheaders = [ ('Content-Type', 'application/pdf'), ('Content-Length', len(pdf)), ('Content-Disposition', f'attachment; filename=Receipt-{payment.receipt_number or "PAY"}.pdf;') ] return request.make_response(pdf, headers=pdfhttpheaders) @http.route(['/my/education/documents'], type='http', auth="user", website=True) def student_documents(self, **kw): student = self._get_student() if not student: return request.redirect('/my/education/dashboard') values = { 'student': student, 'page_name': 'student_documents', 'success': kw.get('success'), } return request.render("education_portal.portal_student_documents", values) @http.route(['/my/education/documents/upload'], type='http', auth="user", methods=['POST'], website=True, csrf=True) def student_documents_upload(self, **post): student = self._get_student() if not student: return request.redirect('/my/education/dashboard') vals = {} for field in ['image', 'doc_passport', 'doc_aadhaar', 'doc_birth_cert', 'doc_transfer_cert', 'doc_community_cert', 'doc_mark_sheets']: file = post.get(field) if file and file.filename: file_content = file.read() vals[field] = base64.b64encode(file_content) filename_field = f"{field}_name" if hasattr(student, filename_field): vals[filename_field] = file.filename if vals: student.sudo().write(vals) return request.redirect('/my/education/documents?success=1') @http.route(['/my/education/attendance'], type='http', auth="user", website=True) def student_attendance(self, **kw): student = self._get_student() if not student: return request.redirect('/my/education/dashboard') attendance_records = student.env['education.attendance'].search([('student_id', '=', student.id)], order='date desc, id desc') total_sessions = len(attendance_records) present_count = len([a for a in attendance_records if a.status == 'present']) absent_count = len([a for a in attendance_records if a.status == 'absent']) late_count = len([a for a in attendance_records if a.status == 'late']) medical_leave_count = len([a for a in attendance_records if a.status == 'medical_leave']) on_duty_count = len([a for a in attendance_records if a.status == 'on_duty']) effective_present = present_count + on_duty_count + (late_count * 0.5) attendance_pct = round((effective_present / total_sessions * 100), 1) if total_sessions > 0 else 100.0 values = { 'student': student, 'attendance_records': attendance_records, 'total_sessions': total_sessions, 'present_count': present_count, 'absent_count': absent_count, 'late_count': late_count, 'medical_leave_count': medical_leave_count, 'on_duty_count': on_duty_count, 'attendance_pct': attendance_pct, 'page_name': 'student_attendance', } return request.render("education_portal.portal_student_attendance", values) @http.route(['/my/education/exam/'], type='http', auth="user", website=True) def student_exam_details(self, exam_id, **kw): student = self._get_student() if not student: return request.redirect('/my/education/dashboard') exam = request.env['education.exam'].sudo().browse(exam_id) if not exam.exists(): return request.redirect('/my/education/dashboard') values = { 'student': student, 'exam': exam, 'page_name': 'student_exam_details', } return request.render("education_portal.portal_student_exam_details", values) @http.route(['/my/education/announcement/'], type='http', auth="user", website=True) def student_announcement_details(self, announcement_id, **kw): student = self._get_student() if not student: return request.redirect('/my/education/dashboard') announcement = request.env['education.announcement'].sudo().browse(announcement_id) if not announcement.exists(): return request.redirect('/my/education/dashboard') values = { 'student': student, 'announcement': announcement, 'page_name': 'student_announcement_details', } return request.render("education_portal.portal_student_announcement_details", values) from odoo.addons.web.controllers.home import Home class StudentLoginRedirect(Home): def _login_redirect(self, uid, redirect=None): redirect_url = super()._login_redirect(uid, redirect=redirect) # Check if the logged-in user is a student user = request.env['res.users'].sudo().browse(uid) # If the user is a student and there is no specific redirect requested # (or the redirect is just the default website home / or /my) if user.student_id and (not redirect or redirect in ['/', '/my']): return '/my/education/dashboard' return redirect_url