diff --git a/routers/devis.py b/routers/devis.py index 514cac0..15e4cf2 100644 --- a/routers/devis.py +++ b/routers/devis.py @@ -1,7 +1,7 @@ import json from datetime import date, timedelta from fastapi import APIRouter, Depends, Request, Form, HTTPException -from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi.responses import HTMLResponse, RedirectResponse, Response from fastapi.templating import Jinja2Templates from sqlalchemy.orm import Session @@ -10,6 +10,7 @@ from models import Devis, LigneDevis, Client, StatutDevis from numerotation import generer_numero_devis from auth import get_current_user from template_helper import render +from config import settings router = APIRouter(prefix="/devis", tags=["devis"], dependencies=[Depends(get_current_user)]) templates = Jinja2Templates(directory="templates") @@ -150,6 +151,27 @@ def changer_statut_devis( db.commit() return RedirectResponse(f"/devis/{devis_id}", status_code=303) +@router.get("/{devis_id}/pdf") +def telecharger_devis__pdf(devis_id: int, db: Session = Depends(get_db)): + from weasyprint import HTML + devis = db.query(Devis).get(devis_id) + if not devis: + raise HTTPException(status_code=404) + + html_content = templates.get_template("pdf/devis.html").render({ + "devis": devis, + "settings": settings, + }) + + pdf_bytes = HTML(string=html_content, base_url=".").write_pdf() + + filename = f"devis-{devis.numero}.pdf" + return Response( + content=pdf_bytes, + media_type="application/pdf", + headers={"Content-Disposition": f'attachment; filename="{filename}"'} + ) + @router.post("/{devis_id}/convertir") def convertir_en_facture(devis_id: int, db: Session = Depends(get_db)): diff --git a/templates/devis/detail.html b/templates/devis/detail.html index c77ec12..5bc0f98 100644 --- a/templates/devis/detail.html +++ b/templates/devis/detail.html @@ -5,6 +5,7 @@

Devis {{ devis.numero }}

← Retour + ⬇ Télécharger PDF {% if devis.statut.value == 'brouillon' %} Modifier {% endif %} diff --git a/templates/devis/liste.html b/templates/devis/liste.html index 984a541..c93de3f 100644 --- a/templates/devis/liste.html +++ b/templates/devis/liste.html @@ -28,6 +28,7 @@ {{ d.statut.value }} Voir + PDF {% endfor %} diff --git a/templates/pdf/devis.html b/templates/pdf/devis.html new file mode 100644 index 0000000..1e6a39e --- /dev/null +++ b/templates/pdf/devis.html @@ -0,0 +1,277 @@ + + + + + + + + + +
+
+

{{ settings.asso_nom }}

+

+ {{ settings.asso_adresse }}
+ {{ settings.asso_code_postal }} {{ settings.asso_ville }}
+ {% if settings.asso_email %}{{ settings.asso_email }}
{% endif %} + {% if settings.asso_telephone %}{{ settings.asso_telephone }}
{% endif %} + {% if settings.asso_rna %}RNA : {{ settings.asso_rna }}
{% endif %} + {% if settings.asso_siret %}SIRET : {{ settings.asso_siret }}{% endif %} +

+
+
+

Devis

+
N° {{ devis.numero }}
+
+ Date d'émission : {{ devis.date_emission.strftime('%d/%m/%Y') }}
+ Date d'échéance : {{ devis.date_validite.strftime('%d/%m/%Y') }} +
+
+
+ + +
+
+

Émetteur

+

+ {{ settings.asso_nom }}
+ {{ settings.asso_adresse }}
+ {{ settings.asso_code_postal }} {{ settings.asso_ville }}
+ {% if settings.asso_rna %}RNA : {{ settings.asso_rna }}
{% endif %} + {% if settings.asso_siret %}SIRET : {{ settings.asso_siret }}{% endif %} +

+
+
+

Destinataire

+

+ {{ devis.client.nom }}
+ {{ devis.client.adresse }}
+ {{ devis.client.code_postal }} {{ devis.client.ville }} + {% if devis.client.siret %}
SIRET : {{ devis.client.siret }}{% endif %} + {% if devis.client.email %}
{{ devis.client.email }}{% endif %} +

+
+
+ + + + + + + + + + + + + {% for l in devis.lignes %} + + + + + + + {% endfor %} + +
DésignationQtéPrix unitaire HTTotal HT
{{ l.description }} + {% if l.quantite == l.quantite | int %}{{ l.quantite | int }}{% else %}{{ l.quantite }}{% endif %} + + {{ "%.2f"|format(l.prix_unitaire_ht) | replace('.', ',') }} € + {{ "%.2f"|format(l.total_ht) | replace('.', ',') }} €
+ + +
+ + + + + + + + + + + + + +
Montant HT{{ "%.2f"|format(devis.total_ht) | replace('.', ',') }} €
TVA0,00 €
Total TTC{{ "%.2f"|format(devis.total_ht) | replace('.', ',') }} €
+
+

TVA non applicable — art. 293B du CGI

+ + + + + +