162 lines
8.2 KiB
Python
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,
|
|
}
|