2026-09-28 11:03:02 +05:30

373 lines
14 KiB
Python

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/<int:session_id>",
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/<int:session_id>/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)