from datetime import timedelta from odoo import fields, http from odoo.http import request from odoo.addons.web.controllers.home import Home STYLE_SELECTION = [ ("bharatanatyam", "Bharatanatyam"), ("kathak", "Kathak"), ("hiphop", "Hip-Hop"), ("contemporary", "Contemporary"), ("zumba", "Zumba"), ] LEVEL_SELECTION = [ ("beginner", "Beginner"), ("intermediate", "Intermediate"), ("advanced", "Advanced"), ] def _float_to_time(value): if not value: return "—" hours = int(value) minutes = int(round((value % 1) * 60)) return "%02d:%02d" % (hours, minutes) class DanceHome(Home): """Send portal parents/teachers to their dashboard after login.""" def _login_redirect(self, uid, redirect=None): if not redirect: user = request.env["res.users"].sudo().browse(uid) landing = user._dance_landing_url() if landing: return landing return super()._login_redirect(uid, redirect) class DanceWebController(http.Controller): # ══════════════════════════════════════════════════════════════════════ # ENROLLMENT # ══════════════════════════════════════════════════════════════════════ def _enroll_values(self, **extra): batches = ( request.env["dance.batch"] .sudo() .search( [("status", "in", ("upcoming", "active"))], order="start_date desc", ) ) values = { "batches": batches, "styles": STYLE_SELECTION, "levels": LEVEL_SELECTION, "form": {}, "error": None, } values.update(extra) return values @http.route("/enroll", type="http", auth="public", website=True, sitemap=True) def enroll_form(self, **kw): return request.render("dance_web.enroll_page", self._enroll_values(form=kw)) @http.route( "/enroll/submit", type="http", auth="public", website=True, methods=["POST"], ) def enroll_submit(self, **post): required = ("student_name", "guardian_name", "guardian_email", "guardian_phone") missing = [field for field in required if not (post.get(field) or "").strip()] if missing: return request.render( "dance_web.enroll_page", self._enroll_values( form=post, error="Please fill in all required fields marked with *.", ), ) vals = { "student_name": post.get("student_name", "").strip(), "date_of_birth": post.get("date_of_birth") or False, "gender": post.get("gender") or False, "guardian_name": post.get("guardian_name", "").strip(), "guardian_email": post.get("guardian_email", "").strip(), "guardian_phone": post.get("guardian_phone", "").strip(), "dance_style": post.get("dance_style") or False, "preferred_level": post.get("preferred_level") or False, "message": post.get("message") or False, } batch_id = post.get("batch_id") if batch_id and batch_id.isdigit(): vals["batch_id"] = int(batch_id) request.env["dance.enrollment.request"].sudo().create(vals) return request.redirect("/enroll/thank-you") @http.route("/enroll/thank-you", type="http", auth="public", website=True) def enroll_thanks(self, **kw): return request.render("dance_web.enroll_thanks", {}) # ══════════════════════════════════════════════════════════════════════ # ROLE DISPATCH # ══════════════════════════════════════════════════════════════════════ @http.route("/dashboard", type="http", auth="user", website=True) def dashboard(self, **kw): landing = request.env.user._dance_landing_url() return request.redirect(landing or "/my") # ══════════════════════════════════════════════════════════════════════ # PARENT DASHBOARD # ══════════════════════════════════════════════════════════════════════ @http.route("/parent", type="http", auth="user", website=True) def parent_dashboard(self, **kw): partner = request.env.user.partner_id today = fields.Date.context_today(request.env.user) students = ( request.env["res.partner"] .sudo() .search( [ ("is_student", "=", True), "|", ("guardian_id", "=", partner.id), ("guardian_ids", "in", partner.id), ], order="name", ) ) cards = [] total_due = 0.0 attendance_values = [] for student in students: attendance = ( request.env["dance.attendance"] .sudo() .search([("student_id", "=", student.id)]) ) total = len(attendance) present = len(attendance.filtered(lambda a: a.status != "absent")) pct = round(present / total * 100) if total else 0 if total: attendance_values.append(pct) invoices = ( request.env["account.move"] .sudo() .search( [ ("move_type", "=", "out_invoice"), ("state", "=", "posted"), ("payment_state", "in", ("not_paid", "partial")), "|", ("dance_student_id", "=", student.id), ("partner_id", "=", student.id), ] ) ) due = sum(invoices.mapped("amount_residual")) total_due += due enrollments = ( request.env["dance.enrollment"] .sudo() .search([("student_id", "=", student.id), ("status", "=", "active")]) ) batches = enrollments.mapped("batch_id") next_session = ( request.env["dance.session"] .sudo() .search( [ ("batch_id", "in", batches.ids), ("date", ">=", today), ("status", "=", "scheduled"), ], order="date asc, start_time asc", limit=1, ) ) cards.append( { "student": student, "attendance_pct": pct, "session_count": total, "fees_due": due, "active_batches": len(batches), "next_session": next_session, } ) overall = ( round(sum(attendance_values) / len(attendance_values)) if attendance_values else 0 ) values = { "user_name": request.env.user.name, "students": students, "cards": cards, "total_due": total_due, "overall_attendance": overall, "currency_symbol": request.env.company.currency_id.symbol or "$", "fmt_time": _float_to_time, } return request.render("dance_web.parent_dashboard", values) # ══════════════════════════════════════════════════════════════════════ # TEACHER DASHBOARD # ══════════════════════════════════════════════════════════════════════ def _teacher_employee(self): return request.env.user.employee_id def _owns_session(self, employee, session): return employee and session.exists() and employee.id in ( session.instructor_id.id, session.substitute_instructor_id.id, ) @http.route("/teacher", type="http", auth="user", website=True) def teacher_dashboard(self, **kw): employee = self._teacher_employee() if not employee: return request.render("dance_web.teacher_no_access", {}) today = fields.Date.context_today(request.env.user) Session = request.env["dance.session"].sudo() instructor_domain = [ "|", ("instructor_id", "=", employee.id), ("substitute_instructor_id", "=", employee.id), ] batches = ( request.env["dance.batch"] .sudo() .search([("instructor_id", "=", employee.id)], order="status, start_date desc") ) todays_sessions = Session.search( [("date", "=", today)] + instructor_domain, order="start_time asc" ) upcoming_sessions = Session.search( [("date", ">", today), ("status", "=", "scheduled")] + instructor_domain, order="date asc, start_time asc", limit=12, ) week_start = today - timedelta(days=today.weekday()) week_end = week_start + timedelta(days=6) week_count = Session.search_count( [("date", ">=", week_start), ("date", "<=", week_end)] + instructor_domain ) active_batches = batches.filtered(lambda b: b.status == "active") students = ( request.env["dance.enrollment"] .sudo() .search( [("batch_id", "in", active_batches.ids), ("status", "=", "active")] ) .mapped("student_id") ) completed = Session.search( [("status", "=", "completed")] + instructor_domain ) hours = sum(max(0.0, s.end_time - s.start_time) for s in completed) values = { "employee": employee, "batches": batches, "todays_sessions": todays_sessions, "upcoming_sessions": upcoming_sessions, "kpi_batches": len(active_batches), "kpi_students": len(students), "kpi_week": week_count, "kpi_hours": round(hours, 1), "fmt_time": _float_to_time, } return request.render("dance_web.teacher_dashboard", values) @http.route( "/teacher/session/", type="http", auth="user", website=True, ) def teacher_session(self, session_id, **kw): employee = self._teacher_employee() session = request.env["dance.session"].sudo().browse(session_id) if not self._owns_session(employee, session): return request.redirect("/teacher") enrollments = ( request.env["dance.enrollment"] .sudo() .search( [("batch_id", "=", session.batch_id.id), ("status", "=", "active")], order="student_id", ) ) attendance_by_student = { line.student_id.id: line for line in session.attendance_ids } roster = [] for enrollment in enrollments: line = attendance_by_student.get(enrollment.student_id.id) roster.append( { "student": enrollment.student_id, "status": line.status if line else "present", } ) return request.render( "dance_web.teacher_session", { "session": session, "roster": roster, "fmt_time": _float_to_time, "saved": kw.get("saved"), }, ) @http.route( "/teacher/session//save", type="http", auth="user", website=True, methods=["POST"], ) def teacher_session_save(self, session_id, **post): employee = self._teacher_employee() session = request.env["dance.session"].sudo().browse(session_id) if not self._owns_session(employee, session): return request.redirect("/teacher") Attendance = request.env["dance.attendance"].sudo() enrollments = ( request.env["dance.enrollment"] .sudo() .search( [("batch_id", "=", session.batch_id.id), ("status", "=", "active")] ) ) attendance_by_student = { line.student_id.id: line for line in session.attendance_ids } for enrollment in enrollments: status = post.get("att_%s" % enrollment.student_id.id) if status not in ("present", "absent", "late"): continue line = attendance_by_student.get(enrollment.student_id.id) if line: line.status = status else: Attendance.create( { "session_id": session.id, "student_id": enrollment.student_id.id, "status": status, } ) return request.redirect("/teacher/session/%s?saved=1" % session.id)