import json from datetime import date, timedelta from fastapi import APIRouter, Depends, Request, Form, HTTPException from fastapi.responses import HTMLResponse, RedirectResponse, Response from fastapi.templating import Jinja2Templates from sqlalchemy.orm import Session from database import get_db 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") @router.get("/", response_class=HTMLResponse) def liste_devis(request: Request, db: Session = Depends(get_db)): devis = db.query(Devis).order_by(Devis.date_emission.desc()).all() return render(templates, "devis/liste.html", request, { "devis": devis }) @router.get("/nouveau", response_class=HTMLResponse) def nouveau_devis_form(request: Request, db: Session = Depends(get_db)): clients = db.query(Client).filter(Client.actif == True).order_by(Client.nom).all() return render(templates, "devis/form.html", request, { "devis": None, "clients": clients, "titre": "Nouveau devis", "date_aujourd_hui": date.today().isoformat(), "date_validite_defaut": (date.today() + timedelta(days=30)).isoformat(), }) @router.post("/nouveau") def creer_devis( request: Request, client_id: int = Form(...), date_emission: str = Form(...), date_validite: str = Form(...), notes: str = Form(""), conditions: str = Form(""), lignes_json: str = Form(...), db: Session = Depends(get_db) ): numero = generer_numero_devis(db) devis = Devis( numero=numero, client_id=client_id, date_emission=date.fromisoformat(date_emission), date_validite=date.fromisoformat(date_validite), notes=notes, conditions=conditions, ) db.add(devis) db.flush() lignes = json.loads(lignes_json) for i, l in enumerate(lignes): ligne = LigneDevis( devis_id=devis.id, description=l["description"], quantite=float(l["quantite"]), prix_unitaire_ht=float(l["prix_unitaire_ht"]), ordre=i ) db.add(ligne) db.commit() return RedirectResponse(f"/devis/{devis.id}", status_code=303) @router.get("/{devis_id}", response_class=HTMLResponse) def voir_devis(request: Request, devis_id: int, db: Session = Depends(get_db)): devis = db.query(Devis).get(devis_id) if not devis: raise HTTPException(status_code=404) return render(templates, "devis/detail.html", request, { "devis": devis, "StatutDevis": StatutDevis }) @router.get("/{devis_id}/modifier", response_class=HTMLResponse) def modifier_devis_form(request: Request, devis_id: int, db: Session = Depends(get_db)): devis = db.query(Devis).get(devis_id) if not devis: raise HTTPException(status_code=404) clients = db.query(Client).filter(Client.actif == True).order_by(Client.nom).all() return render(templates, "devis/form.html", request, { "devis": devis, "clients": clients, "titre": "Modifier le devis", "date_aujourd_hui": date.today().isoformat(), "date_validite_defaut": devis.date_validite.isoformat(), }) @router.post("/{devis_id}/modifier") def modifier_devis( devis_id: int, client_id: int = Form(...), date_emission: str = Form(...), date_validite: str = Form(...), notes: str = Form(""), conditions: str = Form(""), lignes_json: str = Form(...), db: Session = Depends(get_db) ): devis = db.query(Devis).get(devis_id) if not devis: raise HTTPException(status_code=404) devis.client_id = client_id devis.date_emission = date.fromisoformat(date_emission) devis.date_validite = date.fromisoformat(date_validite) devis.notes = notes devis.conditions = conditions for ligne in devis.lignes: db.delete(ligne) db.flush() lignes = json.loads(lignes_json) for i, l in enumerate(lignes): ligne = LigneDevis( devis_id=devis.id, description=l["description"], quantite=float(l["quantite"]), prix_unitaire_ht=float(l["prix_unitaire_ht"]), ordre=i ) db.add(ligne) db.commit() return RedirectResponse(f"/devis/{devis_id}", status_code=303) @router.post("/{devis_id}/statut") def changer_statut_devis( devis_id: int, statut: str = Form(...), db: Session = Depends(get_db) ): devis = db.query(Devis).get(devis_id) if not devis: raise HTTPException(status_code=404) devis.statut = StatutDevis(statut) 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)): from models import Facture, LigneFacture from numerotation import generer_numero_facture devis = db.query(Devis).get(devis_id) if not devis: raise HTTPException(status_code=404) numero = generer_numero_facture(db) facture = Facture( numero=numero, client_id=devis.client_id, devis_id=devis.id, date_emission=date.today(), date_echeance=date.today() + timedelta(days=30), notes=devis.notes, ) db.add(facture) db.flush() for i, l in enumerate(devis.lignes): ligne = LigneFacture( facture_id=facture.id, description=l.description, quantite=l.quantite, prix_unitaire_ht=l.prix_unitaire_ht, ordre=i ) db.add(ligne) devis.statut = StatutDevis.accepte db.commit() return RedirectResponse(f"/factures/{facture.id}", status_code=303)