from urllib.parse import quote from fastapi.staticfiles import StaticFiles from reportlab.lib.units import cm from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from pydantic import BaseModel from io import BytesIO from reportlab.lib.pagesizes import A4 from reportlab.pdfgen import canvas from typing import List import os app = FastAPI() @app.get("/") def home(): return {"message": "API para generar facturas PDF"} app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) class ClientInfo(BaseModel): nombre: str telefono: str direccion: str class ProductItem(BaseModel): numero_de_item: int cantidad: int descripcion: str precio: float class InvoiceData(BaseModel): ruc: str cliente: ClientInfo productos: List[ProductItem] LOGO_PATH = "./img/logo.png" def generate_pdf(data: InvoiceData): buffer = BytesIO() p = canvas.Canvas(buffer, pagesize=A4) width, height = A4 # Establecer márgenes margin_left = 2.54 * cm margin_top = 2.54 * cm # Logo de la empresa logo_width, logo_height = 60, 60 p.drawInlineImage( LOGO_PATH, margin_left, height - margin_top - logo_height + 25, # Ajuste de 10 puntos para el espacio width=logo_width, height=logo_height, ) # Nombre de la empresa y número de RUC p.setFont("Helvetica-Bold", 16) # Alinear a la derecha p.drawRightString(width - margin_left, height - margin_top, f"Arte Visual") p.drawRightString( width - margin_left, height - margin_top - 20, f"Número de RUC: {data.ruc}" ) # Datos del cliente p.setFont("Helvetica", 12) p.drawString(margin_left, height - margin_top - 60, "Datos del cliente:") p.drawString( margin_left, height - margin_top - 80, f"Nombre: {data.cliente.nombre}" ) p.drawString( margin_left, height - margin_top - 100, f"Teléfono: {data.cliente.telefono}" ) p.drawString( margin_left, height - margin_top - 120, f"Dirección: {data.cliente.direccion}", ) # Tabla de productos p.setFont("Helvetica", 12) p.drawString(margin_left, height - margin_top - 140, "Productos adquiridos:") headers = [ "N° de item", "Cantidad", "Nombre o descripción del producto", "Precio", ] col_widths = [70, 70, 250, 70] x_table = margin_left y_table = height - margin_top - 160 # Línea horizontal superior de la tabla p.line(x_table, y_table, x_table + sum(col_widths), y_table) # Encabezados p.setFont("Helvetica", 12) for header, width in zip(headers, col_widths): p.drawString(x_table, y_table - 15, header) x_table += width # Línea horizontal inferior de la tabla p.line(margin_left, y_table - 20, margin_left + sum(col_widths), y_table - 20) # Ejemplo de datos de la tabla y_row = y_table - 40 for row in data.productos: x_table = margin_left for key, width in zip(headers, col_widths): # Manejo específico para el número de ítem if key.lower() == "n° de item": value = row.numero_de_item elif key.lower() == "nombre o descripción del producto": value = row.descripcion elif key.lower() == "precio": # Alinear el precio a la derecha respetando el margen derecho value = f"S/. {row.precio:.2f}" else: # Utilizamos el nombre de la clave directamente para acceder al valor value = row.dict().get(key.lower().replace(" ", "_"), None) p.drawString(x_table, y_row, str(value)) x_table += width y_row -= 15 total = sum(row.cantidad * row.precio for row in data.productos) p.setFont("Helvetica-Bold", 13) p.drawString(margin_left + 370, y_row - 30, f"Total: S/. {total}") p.showPage() p.save() buffer.seek(0) return buffer from fastapi import Request app.mount("/pdf", StaticFiles(directory="pdfs"), name="pdf") @app.post("/generate_pdf") def generate_and_redirect(data: InvoiceData): try: buffer = generate_pdf(data) directorio_actual = os.getcwd() pdfs_directory = os.path.join(directorio_actual, "pdfs") pdfs_directory = os.path.normpath(pdfs_directory) if not os.path.exists(pdfs_directory): os.makedirs(pdfs_directory) file_name = f"factura_{data.cliente.nombre.replace(' ', '_')}.pdf" file_path = os.path.join(pdfs_directory, file_name) file_path = os.path.normpath(file_path) with open(file_path, "wb") as file: file.write(buffer.read()) pdf_url = f"/pdf/{quote(file_name)}" return JSONResponse(content={"pdf_url": pdf_url}) except Exception as e: raise HTTPException(status_code=500, detail=str(e))