05/07 Phase 3: initial codes

This commit is contained in:
2026-05-07 12:17:19 -04:00
parent 6e5518c103
commit ce462c57b2
107 changed files with 6365 additions and 208 deletions
+73 -3
View File
@@ -1,14 +1,20 @@
"""
app/tenant/dashboard/routes.py — Tenant dashboard (placeholder for Phase 3 KPIs).
app/tenant/dashboard/routes.py
Dashboard with Phase 3 KPI widgets scoped to active location.
"""
import logging
from datetime import datetime, timezone, timedelta, date
from decimal import Decimal
from flask import Blueprint, render_template, g
from flask_login import login_required
from app.extensions import db
from app.models.salon import (
Appointment, Transaction, Staff, CheckinQueue, StaffClocking,
)
from app.decorators import require_role
from sqlalchemy import func
logger = logging.getLogger(__name__)
dashboard_bp = Blueprint("dashboard", __name__)
@@ -16,8 +22,72 @@ dashboard_bp = Blueprint("dashboard", __name__)
@login_required
@require_role("tenant_admin", "tenant_manager")
def index():
today = date.today()
day_start = datetime.combine(today, datetime.min.time()).replace(tzinfo=timezone.utc)
day_end = day_start + timedelta(days=1)
location_id = g.location.id if g.location else None
# ── Today's revenue ──────────────────────────────────────
revenue_row = db.session.query(
func.sum(Transaction.total)
).filter_by(
tenant_id=g.tenant.id, location_id=location_id
).filter(
Transaction.created_at >= day_start,
Transaction.created_at < day_end,
Transaction.voided_at.is_(None),
).scalar()
today_revenue = float(revenue_row or 0)
# ── Today's appointments ─────────────────────────────────
appt_counts = dict(
db.session.query(Appointment.status, func.count(Appointment.id))
.filter_by(tenant_id=g.tenant.id, location_id=location_id)
.filter(
Appointment.start_time >= day_start,
Appointment.start_time < day_end,
)
.group_by(Appointment.status)
.all()
)
total_appts = sum(appt_counts.values())
completed_appts = appt_counts.get("completed", 0)
pending_appts = appt_counts.get("pending", 0) + appt_counts.get("confirmed", 0)
# ── Staff on shift ────────────────────────────────────────
staff_on_shift = StaffClocking.query.filter_by(
tenant_id=g.tenant.id, location_id=location_id
).filter(StaffClocking.clocked_out_at.is_(None)).count()
# ── Check-in queue ─────────────────────────────────────────
queue_count = CheckinQueue.query.filter_by(
tenant_id=g.tenant.id, location_id=location_id, status="waiting"
).count()
# ── Upcoming appointments today (next 3) ──────────────────
now = datetime.now(timezone.utc)
upcoming = Appointment.query.filter_by(
tenant_id=g.tenant.id, location_id=location_id
).filter(
Appointment.start_time >= now,
Appointment.start_time < day_end,
Appointment.status.in_(["pending", "confirmed"]),
).order_by(Appointment.start_time).limit(5).all()
kpis = {
"today_revenue": today_revenue,
"total_appointments": total_appts,
"completed_appointments": completed_appts,
"pending_appointments": pending_appts,
"staff_on_shift": staff_on_shift,
"queue_count": queue_count,
}
return render_template(
"tenant/dashboard/index.html",
tenant=g.tenant,
location=g.location,
kpis=kpis,
upcoming_appointments=upcoming,
today=today,
)