Refactorizacion de PDFs y cambio de biblioteca backend 1.0.22-

-Funcional Usuarios, exportar Pdfs front v1.0.25
This commit is contained in:
2025-11-24 21:10:18 -03:00
parent b304fbbb86
commit 4234b71e17
2 changed files with 49 additions and 121 deletions

View File

@@ -1668,126 +1668,32 @@ def export_inspection_to_pdf(
current_user: models.User = Depends(get_current_user), current_user: models.User = Depends(get_current_user),
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
"""Exportar inspección a PDF con imágenes""" """Descargar el PDF guardado en MinIO para la inspección"""
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from reportlab.lib.pagesizes import letter, A4 import requests
from reportlab.lib import colors
from reportlab.lib.units import inch
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image as RLImage, PageBreak
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from io import BytesIO
import base64
# Obtener inspección # Obtener inspección
inspection = db.query(models.Inspection).filter( inspection = db.query(models.Inspection).filter(
models.Inspection.id == inspection_id models.Inspection.id == inspection_id
).first() ).first()
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")
# Verificar permisos (admin, asesor o mecánico dueño)
if current_user.role not in ["admin", "asesor"] and inspection.mechanic_id != current_user.id: if current_user.role not in ["admin", "asesor"] and inspection.mechanic_id != current_user.id:
raise HTTPException(status_code=403, detail="No tienes permisos para ver esta inspección") raise HTTPException(status_code=403, detail="No tienes permisos para ver esta inspección")
# Si existe pdf_url, descargar desde MinIO y devolverlo
# Obtener datos relacionados if inspection.pdf_url:
checklist = db.query(models.Checklist).filter(models.Checklist.id == inspection.checklist_id).first()
mechanic = db.query(models.User).filter(models.User.id == inspection.mechanic_id).first()
answers = db.query(models.Answer).options(
joinedload(models.Answer.media_files)
).join(models.Question).filter(
models.Answer.inspection_id == inspection_id
).order_by(models.Question.section, models.Question.order).all()
# Crear PDF en memoria
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=30, leftMargin=30, topMargin=30, bottomMargin=30)
elements = []
styles = getSampleStyleSheet()
title_style = styles['Title']
normal_style = styles['Normal']
header_style = ParagraphStyle('Header', parent=styles['Heading2'], alignment=TA_CENTER, spaceAfter=12)
# Portada
elements.append(Paragraph(f"Informe de Inspección #{inspection.id}", title_style))
elements.append(Spacer(1, 12))
elements.append(Paragraph(f"Vehículo: {inspection.vehicle_brand or ''} {inspection.vehicle_model or ''} - Placa: {inspection.vehicle_plate}", normal_style))
elements.append(Paragraph(f"Cliente: {inspection.client_name or ''}", normal_style))
elements.append(Paragraph(f"Mecánico: {mechanic.full_name if mechanic else ''}", normal_style))
elements.append(Paragraph(f"Checklist: {checklist.name if checklist else ''}", normal_style))
elements.append(Paragraph(f"Fecha: {inspection.started_at.strftime('%d/%m/%Y %H:%M') if inspection.started_at else ''}", normal_style))
elements.append(Spacer(1, 18))
# Tabla de respuestas
table_data = [["Sección", "Pregunta", "Respuesta", "Estado", "Comentario", "Miniaturas"]]
for ans in answers:
question = ans.question
media_imgs = []
for media in ans.media_files:
if media.file_type == "image":
try: try:
# Descargar imagen y agregar miniatura pdf_resp = requests.get(inspection.pdf_url, stream=True)
import requests if pdf_resp.status_code == 200:
img_resp = requests.get(media.file_path) filename = inspection.pdf_url.split("/")[-1]
if img_resp.status_code == 200: return StreamingResponse(pdf_resp.raw, media_type="application/pdf", headers={
img_bytes = BytesIO(img_resp.content)
rl_img = RLImage(img_bytes, width=0.7*inch, height=0.7*inch)
media_imgs.append(rl_img)
except Exception as e:
print(f"Error cargando imagen {media.file_path}: {e}")
row = [
question.section or "",
question.text,
ans.answer_value,
ans.status,
ans.comment or "",
media_imgs if media_imgs else ""
]
table_data.append(row)
# Construir tabla con miniaturas
table = Table(table_data, colWidths=[1.2*inch, 2.5*inch, 1*inch, 0.8*inch, 2*inch, 1.5*inch])
table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.lightgrey),
('TEXTCOLOR', (0,0), (-1,0), colors.black),
('ALIGN', (0,0), (-1,-1), 'LEFT'),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 10),
('BOTTOMPADDING', (0,0), (-1,0), 8),
('GRID', (0,0), (-1,-1), 0.5, colors.grey),
]))
elements.append(table)
elements.append(Spacer(1, 18))
# Pie de página
elements.append(Paragraph(f"Generado por Checklist Inteligente - {datetime.now().strftime('%d/%m/%Y %H:%M')}", header_style))
# Construir PDF
try:
doc.build(elements)
except Exception as e:
print(f"Error al generar PDF: {e}")
buffer.seek(0)
pdf_size = len(buffer.getvalue())
print(f"PDF generado, tamaño: {pdf_size} bytes")
# Guardar localmente para depuración
try:
with open(f"/tmp/test_inspeccion_{inspection_id}.pdf", "wb") as f:
f.write(buffer.getvalue())
except Exception as e:
print(f"No se pudo guardar PDF local: {e}")
now = datetime.now()
folder = f"{now.year}/{now.month:02d}"
filename = f"inspeccion_{inspection_id}_{inspection.vehicle_plate or 'sin-patente'}.pdf"
s3_key = f"{folder}/{filename}"
# Subir PDF a S3/MinIO
buffer.seek(0) # Asegura que el puntero esté al inicio
s3_client.upload_fileobj(buffer, S3_PDF_BUCKET, s3_key, ExtraArgs={"ContentType": "application/pdf"})
pdf_url = f"{S3_ENDPOINT}/{S3_PDF_BUCKET}/{s3_key}"
inspection.pdf_url = pdf_url
db.commit()
# Descargar el PDF directamente
buffer.seek(0)
return StreamingResponse(buffer, media_type="application/pdf", headers={
"Content-Disposition": f"attachment; filename={filename}" "Content-Disposition": f"attachment; filename={filename}"
}) })
else:
raise HTTPException(status_code=404, detail="No se pudo descargar el PDF desde MinIO")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error al descargar PDF: {e}")
else:
raise HTTPException(status_code=404, detail="La inspección no tiene PDF generado")
# ============= HEALTH CHECK ============= # ============= HEALTH CHECK =============

View File

@@ -1865,8 +1865,9 @@ function InspectionDetailModal({ inspection, user, onClose, onUpdate }) {
const response = await fetch(`${API_URL}/api/inspections/${inspection.id}/pdf`, { const response = await fetch(`${API_URL}/api/inspections/${inspection.id}/pdf`, {
headers: { 'Authorization': `Bearer ${token}` } headers: { 'Authorization': `Bearer ${token}` }
}) })
if (response.ok) { if (response.ok) {
const contentType = response.headers.get('content-type')
if (contentType && contentType.includes('application/pdf')) {
const blob = await response.blob() const blob = await response.blob()
const url = window.URL.createObjectURL(blob) const url = window.URL.createObjectURL(blob)
const a = document.createElement('a') const a = document.createElement('a')
@@ -1876,6 +1877,27 @@ function InspectionDetailModal({ inspection, user, onClose, onUpdate }) {
a.click() a.click()
document.body.removeChild(a) document.body.removeChild(a)
window.URL.revokeObjectURL(url) window.URL.revokeObjectURL(url)
} else {
const data = await response.json()
if (data.pdf_url) {
const pdfRes = await fetch(data.pdf_url)
if (pdfRes.ok) {
const pdfBlob = await pdfRes.blob()
const pdfUrl = window.URL.createObjectURL(pdfBlob)
const a = document.createElement('a')
a.href = pdfUrl
a.download = `inspeccion_${inspection.id}_${inspection.vehicle_plate || 'sin-patente'}.pdf`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
window.URL.revokeObjectURL(pdfUrl)
} else {
alert('No se pudo descargar el PDF desde MinIO')
}
} else {
alert('No se encontró la URL del PDF')
}
}
} else { } else {
alert('Error al generar PDF') alert('Error al generar PDF')
} }