Cambios Adicionales

 Importado or_ de SQLAlchemy para query del reporte
 Backend: 1.0.86 → 1.0.87
🎯 Resultado
 Inspecciones solo muestran preguntas activas del checklist
 PDFs correctos sin preguntas eliminadas
 Cálculo de score preciso (solo preguntas vigentes)
 Webhooks envían solo datos relevantes
 Reportes con métricas correctas
 Respuestas huérfanas de preguntas eliminadas se ignoran automáticamente
This commit is contained in:
2025-12-01 00:10:06 -03:00
parent 54006d5756
commit 4174774702

View File

@@ -5,7 +5,7 @@ from fastapi import FastAPI, File, UploadFile, Form, Depends, HTTPException, sta
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from sqlalchemy import func, case from sqlalchemy import func, case, or_
from typing import List, Optional from typing import List, Optional
from io import BytesIO from io import BytesIO
import os import os
@@ -100,11 +100,14 @@ def send_completed_inspection_to_n8n(inspection, db):
# Obtener checklist # Obtener checklist
checklist = db.query(models.Checklist).filter(models.Checklist.id == inspection.checklist_id).first() checklist = db.query(models.Checklist).filter(models.Checklist.id == inspection.checklist_id).first()
# Obtener todas las respuestas con sus imágenes # Obtener todas las respuestas con sus imágenes - SOLO de preguntas NO eliminadas
answers = db.query(models.Answer).options( answers = db.query(models.Answer).options(
joinedload(models.Answer.media_files), joinedload(models.Answer.media_files),
joinedload(models.Answer.question) joinedload(models.Answer.question)
).filter(models.Answer.inspection_id == inspection.id).all() ).join(models.Question).filter(
models.Answer.inspection_id == inspection.id,
models.Question.is_deleted == False # Excluir preguntas eliminadas
).all()
# Preparar respuestas con imágenes # Preparar respuestas con imágenes
respuestas_data = [] respuestas_data = []
@@ -204,7 +207,7 @@ def send_completed_inspection_to_n8n(inspection, db):
# No lanzamos excepción para no interrumpir el flujo normal # No lanzamos excepción para no interrumpir el flujo normal
BACKEND_VERSION = "1.0.86" BACKEND_VERSION = "1.0.87"
app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION) app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
# S3/MinIO configuration # S3/MinIO configuration
@@ -1226,7 +1229,7 @@ def get_inspection(
current_user: models.User = Depends(get_current_user) current_user: models.User = Depends(get_current_user)
): ):
inspection = db.query(models.Inspection).options( inspection = db.query(models.Inspection).options(
joinedload(models.Inspection.checklist).joinedload(models.Checklist.questions), joinedload(models.Inspection.checklist),
joinedload(models.Inspection.mechanic), joinedload(models.Inspection.mechanic),
joinedload(models.Inspection.answers).joinedload(models.Answer.question), joinedload(models.Inspection.answers).joinedload(models.Answer.question),
joinedload(models.Inspection.answers).joinedload(models.Answer.media_files) joinedload(models.Inspection.answers).joinedload(models.Answer.media_files)
@@ -1235,6 +1238,13 @@ def get_inspection(
if not inspection: if not inspection:
raise HTTPException(status_code=404, detail="Inspección no encontrada") raise HTTPException(status_code=404, detail="Inspección no encontrada")
# Cargar solo preguntas NO eliminadas del checklist
if inspection.checklist:
inspection.checklist.questions = db.query(models.Question).filter(
models.Question.checklist_id == inspection.checklist.id,
models.Question.is_deleted == False
).order_by(models.Question.order).all()
return inspection return inspection
@@ -1672,12 +1682,13 @@ def generate_inspection_pdf(inspection_id: int, db: Session) -> str:
elements.append(Paragraph("📝 DETALLE DE LA INSPECCIÓN", section_header_style)) elements.append(Paragraph("📝 DETALLE DE LA INSPECCIÓN", section_header_style))
elements.append(Spacer(1, 5*mm)) elements.append(Spacer(1, 5*mm))
# Obtener respuestas agrupadas por sección # Obtener respuestas agrupadas por sección - SOLO de preguntas NO eliminadas
answers = db.query(models.Answer).options( answers = db.query(models.Answer).options(
joinedload(models.Answer.media_files), joinedload(models.Answer.media_files),
joinedload(models.Answer.question) joinedload(models.Answer.question)
).join(models.Question).filter( ).join(models.Question).filter(
models.Answer.inspection_id == inspection_id models.Answer.inspection_id == inspection_id,
models.Question.is_deleted == False # Excluir preguntas eliminadas
).order_by( ).order_by(
models.Question.section, models.Question.section,
models.Question.order models.Question.order
@@ -1905,8 +1916,11 @@ def complete_inspection(
if not inspection: if not inspection:
raise HTTPException(status_code=404, detail="Inspección no encontrada") raise HTTPException(status_code=404, detail="Inspección no encontrada")
# Calcular score # Calcular score - SOLO de preguntas NO eliminadas
answers = db.query(models.Answer).filter(models.Answer.inspection_id == inspection_id).all() answers = db.query(models.Answer).join(models.Question).filter(
models.Answer.inspection_id == inspection_id,
models.Question.is_deleted == False # Excluir preguntas eliminadas
).all()
total_score = sum(a.points_earned for a in answers) total_score = sum(a.points_earned for a in answers)
flagged_count = sum(1 for a in answers if a.is_flagged) flagged_count = sum(1 for a in answers if a.is_flagged)
@@ -2078,9 +2092,10 @@ def update_answer(
if inspection.status == "completed": if inspection.status == "completed":
print(f"🔄 Regenerando PDF para inspección completada #{inspection.id}") print(f"🔄 Regenerando PDF para inspección completada #{inspection.id}")
# Recalcular score de la inspección # Recalcular score de la inspección - SOLO de preguntas NO eliminadas
answers = db.query(models.Answer).filter( answers = db.query(models.Answer).join(models.Question).filter(
models.Answer.inspection_id == inspection.id models.Answer.inspection_id == inspection.id,
models.Question.is_deleted == False # Excluir preguntas eliminadas
).all() ).all()
inspection.score = sum(a.points_earned for a in answers) inspection.score = sum(a.points_earned for a in answers)
@@ -2243,9 +2258,10 @@ def admin_edit_answer(
if inspection and inspection.status == "completed": if inspection and inspection.status == "completed":
print(f"🔄 Regenerando PDF para inspección completada #{inspection.id} (admin-edit)") print(f"🔄 Regenerando PDF para inspección completada #{inspection.id} (admin-edit)")
# Recalcular score de la inspección # Recalcular score de la inspección - SOLO de preguntas NO eliminadas
answers = db.query(models.Answer).filter( answers = db.query(models.Answer).join(models.Question).filter(
models.Answer.inspection_id == inspection.id models.Answer.inspection_id == inspection.id,
models.Question.is_deleted == False # Excluir preguntas eliminadas
).all() ).all()
inspection.score = sum(a.points_earned for a in answers) inspection.score = sum(a.points_earned for a in answers)
@@ -3298,7 +3314,11 @@ def get_inspections_report(
.join(models.Checklist, models.Inspection.checklist_id == models.Checklist.id)\ .join(models.Checklist, models.Inspection.checklist_id == models.Checklist.id)\
.join(models.User, models.Inspection.mechanic_id == models.User.id)\ .join(models.User, models.Inspection.mechanic_id == models.User.id)\
.outerjoin(models.Answer, models.Answer.inspection_id == models.Inspection.id)\ .outerjoin(models.Answer, models.Answer.inspection_id == models.Inspection.id)\
.filter(models.Inspection.is_active == True) .outerjoin(models.Question, models.Answer.question_id == models.Question.id)\
.filter(
models.Inspection.is_active == True,
or_(models.Question.is_deleted == False, models.Question.id == None) # Solo contar answers de preguntas no eliminadas o si no hay answer
)
# Aplicar filtros # Aplicar filtros
if start_date: if start_date: