2026-09-25 18:00:58 +05:30

284 lines
12 KiB
Python

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/<int:payment_id>'], 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/<int:exam_id>'], 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/<int:announcement_id>'], 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)