backend actualizado para dashboard
This commit is contained in:
@@ -2,7 +2,8 @@ from fastapi import FastAPI, Depends, HTTPException, status, UploadFile, File
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from typing import List
|
||||
from sqlalchemy import func, case
|
||||
from typing import List, Optional
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime, timedelta
|
||||
@@ -1273,6 +1274,286 @@ Responde en formato JSON:
|
||||
}
|
||||
|
||||
|
||||
# ============= REPORTS =============
|
||||
@app.get("/api/reports/dashboard", response_model=schemas.DashboardData)
|
||||
def get_dashboard_data(
|
||||
start_date: Optional[str] = None,
|
||||
end_date: Optional[str] = None,
|
||||
mechanic_id: Optional[int] = None,
|
||||
current_user: models.User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Obtener datos del dashboard de informes"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Solo administradores pueden acceder a reportes")
|
||||
|
||||
# Construir query base
|
||||
query = db.query(models.Inspection)
|
||||
|
||||
# Aplicar filtros de fecha
|
||||
if start_date:
|
||||
start = datetime.fromisoformat(start_date)
|
||||
query = query.filter(models.Inspection.started_at >= start)
|
||||
if end_date:
|
||||
end = datetime.fromisoformat(end_date)
|
||||
query = query.filter(models.Inspection.started_at <= end)
|
||||
|
||||
# Filtro por mecánico
|
||||
if mechanic_id:
|
||||
query = query.filter(models.Inspection.mechanic_id == mechanic_id)
|
||||
|
||||
# Solo inspecciones activas
|
||||
query = query.filter(models.Inspection.is_active == True)
|
||||
|
||||
# ESTADÍSTICAS GENERALES
|
||||
total = query.count()
|
||||
completed = query.filter(models.Inspection.status == "completed").count()
|
||||
pending = total - completed
|
||||
|
||||
# Score promedio
|
||||
avg_score_result = query.filter(
|
||||
models.Inspection.score.isnot(None),
|
||||
models.Inspection.max_score.isnot(None),
|
||||
models.Inspection.max_score > 0
|
||||
).with_entities(
|
||||
func.avg(models.Inspection.score * 100.0 / models.Inspection.max_score)
|
||||
).scalar()
|
||||
avg_score = round(avg_score_result, 2) if avg_score_result else 0.0
|
||||
|
||||
# Items señalados
|
||||
flagged_items = db.query(func.count(models.Answer.id))\
|
||||
.filter(models.Answer.is_flagged == True)\
|
||||
.join(models.Inspection)\
|
||||
.filter(models.Inspection.is_active == True)
|
||||
|
||||
if start_date:
|
||||
start = datetime.fromisoformat(start_date)
|
||||
flagged_items = flagged_items.filter(models.Inspection.started_at >= start)
|
||||
if end_date:
|
||||
end = datetime.fromisoformat(end_date)
|
||||
flagged_items = flagged_items.filter(models.Inspection.started_at <= end)
|
||||
if mechanic_id:
|
||||
flagged_items = flagged_items.filter(models.Inspection.mechanic_id == mechanic_id)
|
||||
|
||||
total_flagged = flagged_items.scalar() or 0
|
||||
|
||||
stats = schemas.InspectionStats(
|
||||
total_inspections=total,
|
||||
completed_inspections=completed,
|
||||
pending_inspections=pending,
|
||||
completion_rate=round((completed / total * 100) if total > 0 else 0, 2),
|
||||
avg_score=avg_score,
|
||||
total_flagged_items=total_flagged
|
||||
)
|
||||
|
||||
# RANKING DE MECÁNICOS
|
||||
mechanic_stats = db.query(
|
||||
models.User.id,
|
||||
models.User.full_name,
|
||||
func.count(models.Inspection.id).label('total'),
|
||||
func.avg(
|
||||
case(
|
||||
(models.Inspection.max_score > 0, models.Inspection.score * 100.0 / models.Inspection.max_score),
|
||||
else_=None
|
||||
)
|
||||
).label('avg_score'),
|
||||
func.count(case((models.Inspection.status == 'completed', 1))).label('completed')
|
||||
).join(models.Inspection, models.Inspection.mechanic_id == models.User.id)\
|
||||
.filter(models.User.role.in_(['mechanic', 'mecanico']))\
|
||||
.filter(models.User.is_active == True)\
|
||||
.filter(models.Inspection.is_active == True)
|
||||
|
||||
if start_date:
|
||||
start = datetime.fromisoformat(start_date)
|
||||
mechanic_stats = mechanic_stats.filter(models.Inspection.started_at >= start)
|
||||
if end_date:
|
||||
end = datetime.fromisoformat(end_date)
|
||||
mechanic_stats = mechanic_stats.filter(models.Inspection.started_at <= end)
|
||||
|
||||
mechanic_stats = mechanic_stats.group_by(models.User.id, models.User.full_name)\
|
||||
.order_by(func.count(models.Inspection.id).desc())\
|
||||
.all()
|
||||
|
||||
mechanic_ranking = [
|
||||
schemas.MechanicRanking(
|
||||
mechanic_id=m.id,
|
||||
mechanic_name=m.full_name,
|
||||
total_inspections=m.total,
|
||||
avg_score=round(m.avg_score, 2) if m.avg_score else 0.0,
|
||||
completion_rate=round((m.completed / m.total * 100) if m.total > 0 else 0, 2)
|
||||
)
|
||||
for m in mechanic_stats
|
||||
]
|
||||
|
||||
# ESTADÍSTICAS POR CHECKLIST
|
||||
checklist_stats_query = db.query(
|
||||
models.Checklist.id,
|
||||
models.Checklist.name,
|
||||
func.count(models.Inspection.id).label('total'),
|
||||
func.avg(
|
||||
case(
|
||||
(models.Inspection.max_score > 0, models.Inspection.score * 100.0 / models.Inspection.max_score),
|
||||
else_=None
|
||||
)
|
||||
).label('avg_score')
|
||||
).join(models.Inspection)\
|
||||
.filter(models.Inspection.is_active == True)\
|
||||
.filter(models.Checklist.is_active == True)
|
||||
|
||||
if start_date:
|
||||
start = datetime.fromisoformat(start_date)
|
||||
checklist_stats_query = checklist_stats_query.filter(models.Inspection.started_at >= start)
|
||||
if end_date:
|
||||
end = datetime.fromisoformat(end_date)
|
||||
checklist_stats_query = checklist_stats_query.filter(models.Inspection.started_at <= end)
|
||||
if mechanic_id:
|
||||
checklist_stats_query = checklist_stats_query.filter(models.Inspection.mechanic_id == mechanic_id)
|
||||
|
||||
checklist_stats_query = checklist_stats_query.group_by(models.Checklist.id, models.Checklist.name)
|
||||
checklist_stats_data = checklist_stats_query.all()
|
||||
|
||||
checklist_stats = [
|
||||
schemas.ChecklistStats(
|
||||
checklist_id=c.id,
|
||||
checklist_name=c.name,
|
||||
total_inspections=c.total,
|
||||
avg_score=round(c.avg_score, 2) if c.avg_score else 0.0
|
||||
)
|
||||
for c in checklist_stats_data
|
||||
]
|
||||
|
||||
# INSPECCIONES POR FECHA (últimos 30 días)
|
||||
end_date_obj = datetime.fromisoformat(end_date) if end_date else datetime.now()
|
||||
start_date_obj = datetime.fromisoformat(start_date) if start_date else end_date_obj - timedelta(days=30)
|
||||
|
||||
inspections_by_date_query = db.query(
|
||||
func.date(models.Inspection.started_at).label('date'),
|
||||
func.count(models.Inspection.id).label('count')
|
||||
).filter(
|
||||
models.Inspection.started_at.between(start_date_obj, end_date_obj),
|
||||
models.Inspection.is_active == True
|
||||
)
|
||||
|
||||
if mechanic_id:
|
||||
inspections_by_date_query = inspections_by_date_query.filter(
|
||||
models.Inspection.mechanic_id == mechanic_id
|
||||
)
|
||||
|
||||
inspections_by_date_data = inspections_by_date_query.group_by(
|
||||
func.date(models.Inspection.started_at)
|
||||
).all()
|
||||
|
||||
inspections_by_date = {
|
||||
str(d.date): d.count for d in inspections_by_date_data
|
||||
}
|
||||
|
||||
# RATIO PASS/FAIL
|
||||
pass_fail_data = db.query(
|
||||
models.Answer.answer_value,
|
||||
func.count(models.Answer.id).label('count')
|
||||
).join(models.Inspection)\
|
||||
.filter(models.Inspection.is_active == True)\
|
||||
.filter(models.Answer.answer_value.in_(['pass', 'fail', 'good', 'bad', 'regular']))
|
||||
|
||||
if start_date:
|
||||
start = datetime.fromisoformat(start_date)
|
||||
pass_fail_data = pass_fail_data.filter(models.Inspection.started_at >= start)
|
||||
if end_date:
|
||||
end = datetime.fromisoformat(end_date)
|
||||
pass_fail_data = pass_fail_data.filter(models.Inspection.started_at <= end)
|
||||
if mechanic_id:
|
||||
pass_fail_data = pass_fail_data.filter(models.Inspection.mechanic_id == mechanic_id)
|
||||
|
||||
pass_fail_data = pass_fail_data.group_by(models.Answer.answer_value).all()
|
||||
|
||||
pass_fail_ratio = {d.answer_value: d.count for d in pass_fail_data}
|
||||
|
||||
return schemas.DashboardData(
|
||||
stats=stats,
|
||||
mechanic_ranking=mechanic_ranking,
|
||||
checklist_stats=checklist_stats,
|
||||
inspections_by_date=inspections_by_date,
|
||||
pass_fail_ratio=pass_fail_ratio
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/reports/inspections")
|
||||
def get_inspections_report(
|
||||
start_date: Optional[str] = None,
|
||||
end_date: Optional[str] = None,
|
||||
mechanic_id: Optional[int] = None,
|
||||
checklist_id: Optional[int] = None,
|
||||
status: Optional[str] = None,
|
||||
limit: int = 100,
|
||||
current_user: models.User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Obtener lista de inspecciones con filtros"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Solo administradores pueden acceder a reportes")
|
||||
|
||||
# Query base con select_from explícito
|
||||
query = db.query(
|
||||
models.Inspection.id,
|
||||
models.Inspection.vehicle_plate,
|
||||
models.Checklist.name.label('checklist_name'),
|
||||
models.User.full_name.label('mechanic_name'),
|
||||
models.Inspection.status,
|
||||
models.Inspection.score,
|
||||
models.Inspection.max_score,
|
||||
models.Inspection.started_at,
|
||||
models.Inspection.completed_at,
|
||||
func.coalesce(
|
||||
func.count(case((models.Answer.is_flagged == True, 1))),
|
||||
0
|
||||
).label('flagged_items')
|
||||
).select_from(models.Inspection)\
|
||||
.join(models.Checklist, models.Inspection.checklist_id == models.Checklist.id)\
|
||||
.join(models.User, models.Inspection.mechanic_id == models.User.id)\
|
||||
.outerjoin(models.Answer, models.Answer.inspection_id == models.Inspection.id)\
|
||||
.filter(models.Inspection.is_active == True)
|
||||
|
||||
# Aplicar filtros
|
||||
if start_date:
|
||||
start = datetime.fromisoformat(start_date)
|
||||
query = query.filter(models.Inspection.started_at >= start)
|
||||
if end_date:
|
||||
end = datetime.fromisoformat(end_date)
|
||||
query = query.filter(models.Inspection.started_at <= end)
|
||||
if mechanic_id:
|
||||
query = query.filter(models.Inspection.mechanic_id == mechanic_id)
|
||||
if checklist_id:
|
||||
query = query.filter(models.Inspection.checklist_id == checklist_id)
|
||||
if status:
|
||||
query = query.filter(models.Inspection.status == status)
|
||||
|
||||
# Group by y order
|
||||
query = query.group_by(
|
||||
models.Inspection.id,
|
||||
models.Checklist.name,
|
||||
models.User.full_name
|
||||
).order_by(models.Inspection.started_at.desc())\
|
||||
.limit(limit)
|
||||
|
||||
results = query.all()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": r.id,
|
||||
"vehicle_plate": r.vehicle_plate,
|
||||
"checklist_name": r.checklist_name,
|
||||
"mechanic_name": r.mechanic_name,
|
||||
"status": r.status,
|
||||
"score": r.score,
|
||||
"max_score": r.max_score,
|
||||
"flagged_items": r.flagged_items,
|
||||
"started_at": r.started_at.isoformat() if r.started_at else None,
|
||||
"completed_at": r.completed_at.isoformat() if r.completed_at else None
|
||||
}
|
||||
for r in results
|
||||
]
|
||||
|
||||
|
||||
# ============= HEALTH CHECK =============
|
||||
@app.get("/")
|
||||
def root():
|
||||
|
||||
Reference in New Issue
Block a user