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

162 lines
8.2 KiB
Python

# pyrefly: ignore [missing-import]
from odoo import http, fields
# pyrefly: ignore [missing-import]
from odoo.http import request
import datetime
class EducationDashboardController(http.Controller):
@http.route(['/web/education/dashboard/kpis'], type='json', auth="user")
def get_dashboard_kpis(self, campus_id=None, program_id=None, academic_year_id=None, semester_id=None, env=None, **kw):
env = env or request.env
# ── Filters ──────────────────────────────────────────────────────────
student_domain = []
if campus_id:
student_domain.append(('campus_id', '=', int(campus_id)))
if program_id:
student_domain.append(('program_id', '=', int(program_id)))
if academic_year_id:
student_domain.append(('academic_year_id', '=', int(academic_year_id)))
if semester_id:
student_domain.append(('semester_id', '=', int(semester_id)))
# ── Students ─────────────────────────────────────────────────────────
students = env['education.student'].search(student_domain)
total_students = len(students)
active_students = len(students.filtered(lambda s: s.status == 'active'))
admissions = len(students.filtered(lambda s: s.status == 'admitted'))
graduates = len(students.filtered(lambda s: s.status == 'graduated'))
# ── Finance ───────────────────────────────────────────────────────────
total_fees = sum(students.mapped('total_fees'))
collected_fees = sum(students.mapped('total_paid'))
outstanding_fees = sum(students.mapped('outstanding_amount'))
fee_percent = int(collected_fees / total_fees * 100) if total_fees > 0 else 0
# ── Academic counts ───────────────────────────────────────────────────
programs_count = env['education.program'].search_count([])
dept_count = env['education.department'].search_count([])
campus_count = env['education.campus'].search_count([])
years_count = env['education.academic.year'].search_count([])
# ── Charts: Program distribution ──────────────────────────────────────
program_chart = []
for gd in env['education.student'].read_group(student_domain, ['program_id'], ['program_id']):
if gd.get('program_id'):
program_chart.append({'name': gd['program_id'][1], 'count': gd['program_id_count']})
# ── Charts: Campus distribution ───────────────────────────────────────
campus_chart = []
for gd in env['education.student'].read_group(student_domain, ['campus_id'], ['campus_id']):
if gd.get('campus_id'):
campus_chart.append({'name': gd['campus_id'][1], 'count': gd['campus_id_count']})
# ── Monthly enrollment trend (current calendar year) ─────────────────
month_names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
cur_year = datetime.date.today().year
enrollment_trend = []
cumulative = 0
for m in range(1, 13):
ny = cur_year if m < 12 else cur_year + 1
nm = m + 1 if m < 12 else 1
count = env['education.student'].search_count([
('create_date', '>=', f'{cur_year}-{m:02d}-01'),
('create_date', '<', f'{ny}-{nm:02d}-01'),
])
cumulative += count
enrollment_trend.append({
'month': month_names[m - 1],
'new': count,
'total': cumulative if cumulative else total_students,
})
# ── Upcoming exams ────────────────────────────────────────────────────
upcoming_exams = []
try:
exams = env['education.exam'].search(
[('state', 'not in', ['cancelled', 'completed', 'done'])],
order='date asc', limit=4
)
for e in exams:
upcoming_exams.append({
'name': e.name,
'date': e.date.strftime('%b %d, %Y') if e.date else '',
'program': e.program_id.name if getattr(e, 'program_id', False) and e.program_id else '',
})
except Exception:
pass
# ── Recent activities ─────────────────────────────────────────────────
recent_activities = []
try:
msgs = env['mail.message'].search([
('model', 'in', ['education.student', 'education.exam', 'education.fee.payment']),
('message_type', 'in', ['email', 'comment']),
], order='date desc', limit=5)
for msg in msgs:
body = msg.subject or ''
if not body and msg.body:
import re
body = re.sub('<[^>]+>', '', msg.body)[:60]
recent_activities.append({
'text': body or 'Activity recorded',
'time': msg.date.strftime('%I:%M %p') if msg.date else '',
'model': msg.model or '',
})
except Exception:
pass
# ── User / date context ───────────────────────────────────────────────
user_name = env.user.name
today_str = datetime.date.today().strftime('%A, %b %d, %Y')
current_year_name = ''
try:
cy = env['education.academic.year'].search([('current_year', '=', True)], limit=1)
if cy:
current_year_name = cy.name
except Exception:
pass
# ── Filter dropdown data ──────────────────────────────────────────────
campuses = [{'id': c.id, 'name': c.name} for c in env['education.campus'].search([])]
programs = [{'id': p.id, 'name': p.name} for p in env['education.program'].search([])]
years = [{'id': y.id, 'name': y.name} for y in env['education.academic.year'].search([])]
semesters = [{'id': s.id, 'name': s.name} for s in env['education.semester'].search([])]
return {
# student KPIs
'total_students': total_students,
'active_students': active_students,
'admissions': admissions,
'graduates': graduates,
# finance
'total_fees': total_fees,
'collected_fees': collected_fees,
'outstanding_fees': outstanding_fees,
'fee_percent': fee_percent,
# academic
'programs_count': programs_count,
'dept_count': dept_count,
'campus_count': campus_count,
'years_count': years_count,
# charts
'program_chart': program_chart,
'campus_chart': campus_chart,
'enrollment_trend': enrollment_trend,
# operational
'upcoming_exams': upcoming_exams,
'recent_activities': recent_activities,
# context
'user_name': user_name,
'today': today_str,
'current_year_name': current_year_name,
# filter options
'campuses': campuses,
'programs': programs,
'years': years,
'semesters': semesters,
}