Refactorizacion de PDFs y cambio de biblioteca backend 1.0.22-
-Funcional Usuarios, exportar Pdfs front v1.0.25
This commit is contained in:
@@ -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()
|
try:
|
||||||
mechanic = db.query(models.User).filter(models.User.id == inspection.mechanic_id).first()
|
pdf_resp = requests.get(inspection.pdf_url, stream=True)
|
||||||
answers = db.query(models.Answer).options(
|
if pdf_resp.status_code == 200:
|
||||||
joinedload(models.Answer.media_files)
|
filename = inspection.pdf_url.split("/")[-1]
|
||||||
).join(models.Question).filter(
|
return StreamingResponse(pdf_resp.raw, media_type="application/pdf", headers={
|
||||||
models.Answer.inspection_id == inspection_id
|
"Content-Disposition": f"attachment; filename={filename}"
|
||||||
).order_by(models.Question.section, models.Question.order).all()
|
})
|
||||||
|
else:
|
||||||
# Crear PDF en memoria
|
raise HTTPException(status_code=404, detail="No se pudo descargar el PDF desde MinIO")
|
||||||
buffer = BytesIO()
|
except Exception as e:
|
||||||
doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=30, leftMargin=30, topMargin=30, bottomMargin=30)
|
raise HTTPException(status_code=500, detail=f"Error al descargar PDF: {e}")
|
||||||
elements = []
|
else:
|
||||||
styles = getSampleStyleSheet()
|
raise HTTPException(status_code=404, detail="La inspección no tiene PDF generado")
|
||||||
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:
|
|
||||||
# Descargar imagen y agregar miniatura
|
|
||||||
import requests
|
|
||||||
img_resp = requests.get(media.file_path)
|
|
||||||
if img_resp.status_code == 200:
|
|
||||||
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}"
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
# ============= HEALTH CHECK =============
|
# ============= HEALTH CHECK =============
|
||||||
|
|||||||
@@ -1865,17 +1865,39 @@ 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 blob = await response.blob()
|
const contentType = response.headers.get('content-type')
|
||||||
const url = window.URL.createObjectURL(blob)
|
if (contentType && contentType.includes('application/pdf')) {
|
||||||
const a = document.createElement('a')
|
const blob = await response.blob()
|
||||||
a.href = url
|
const url = window.URL.createObjectURL(blob)
|
||||||
a.download = `inspeccion_${inspection.id}_${inspection.vehicle_plate || 'sin-patente'}.pdf`
|
const a = document.createElement('a')
|
||||||
document.body.appendChild(a)
|
a.href = url
|
||||||
a.click()
|
a.download = `inspeccion_${inspection.id}_${inspection.vehicle_plate || 'sin-patente'}.pdf`
|
||||||
document.body.removeChild(a)
|
document.body.appendChild(a)
|
||||||
window.URL.revokeObjectURL(url)
|
a.click()
|
||||||
|
document.body.removeChild(a)
|
||||||
|
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')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user