373 lines
14 KiB
Python
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)
|