Init project

This commit is contained in:
seb
2026-02-21 23:26:50 +01:00
parent df61e93871
commit b7046b125c
29 changed files with 2553 additions and 0 deletions

189
routers/factures.py Normal file
View File

@@ -0,0 +1,189 @@
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 Facture, LigneFacture, Client, StatutFacture
from numerotation import generer_numero_facture
from config import settings
from auth import get_current_user
from template_helper import render
router = APIRouter(prefix="/factures", tags=["factures"], dependencies=[Depends(get_current_user)])
templates = Jinja2Templates(directory="templates")
@router.get("/", response_class=HTMLResponse)
def liste_factures(request: Request, db: Session = Depends(get_db)):
factures = db.query(Facture).order_by(Facture.date_emission.desc()).all()
return render(templates, "factures/liste.html", request, {
"factures": factures
})
@router.get("/nouvelle", response_class=HTMLResponse)
def nouvelle_facture_form(request: Request, db: Session = Depends(get_db)):
clients = db.query(Client).filter(Client.actif == True).order_by(Client.nom).all()
return render(templates, "factures/form.html", request, {
"facture": None,
"clients": clients,
"titre": "Nouvelle facture",
"date_aujourd_hui": date.today().isoformat(),
"date_echeance_defaut": (date.today() + timedelta(days=30)).isoformat(),
})
@router.post("/nouvelle")
def creer_facture(
client_id: int = Form(...),
date_emission: str = Form(...),
date_echeance: str = Form(...),
notes: str = Form(""),
conditions_reglement: str = Form("Paiement à réception de facture."),
lignes_json: str = Form(...),
db: Session = Depends(get_db)
):
numero = generer_numero_facture(db)
facture = Facture(
numero=numero,
client_id=client_id,
date_emission=date.fromisoformat(date_emission),
date_echeance=date.fromisoformat(date_echeance),
notes=notes,
conditions_reglement=conditions_reglement,
)
db.add(facture)
db.flush()
lignes = json.loads(lignes_json)
for i, l in enumerate(lignes):
ligne = LigneFacture(
facture_id=facture.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"/factures/{facture.id}", status_code=303)
@router.get("/{facture_id}", response_class=HTMLResponse)
def voir_facture(request: Request, facture_id: int, db: Session = Depends(get_db)):
facture = db.query(Facture).get(facture_id)
if not facture:
raise HTTPException(status_code=404)
return render(templates, "factures/detail.html", request, {
"facture": facture, "StatutFacture": StatutFacture
})
@router.get("/{facture_id}/modifier", response_class=HTMLResponse)
def modifier_facture_form(request: Request, facture_id: int, db: Session = Depends(get_db)):
facture = db.query(Facture).get(facture_id)
if not facture:
raise HTTPException(status_code=404)
if facture.statut != StatutFacture.emise:
raise HTTPException(status_code=400, detail="Seules les factures émises peuvent être modifiées.")
clients = db.query(Client).filter(Client.actif == True).order_by(Client.nom).all()
return render(templates, "factures/form.html", request, {
"facture": facture,
"clients": clients,
"titre": "Modifier la facture",
"date_aujourd_hui": facture.date_emission.isoformat(),
"date_echeance_defaut": facture.date_echeance.isoformat(),
})
@router.post("/{facture_id}/modifier")
def modifier_facture(
facture_id: int,
client_id: int = Form(...),
date_emission: str = Form(...),
date_echeance: str = Form(...),
notes: str = Form(""),
conditions_reglement: str = Form(""),
lignes_json: str = Form(...),
db: Session = Depends(get_db)
):
facture = db.query(Facture).get(facture_id)
if not facture:
raise HTTPException(status_code=404)
facture.client_id = client_id
facture.date_emission = date.fromisoformat(date_emission)
facture.date_echeance = date.fromisoformat(date_echeance)
facture.notes = notes
facture.conditions_reglement = conditions_reglement
for ligne in facture.lignes:
db.delete(ligne)
db.flush()
lignes = json.loads(lignes_json)
for i, l in enumerate(lignes):
ligne = LigneFacture(
facture_id=facture.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"/factures/{facture_id}", status_code=303)
@router.post("/{facture_id}/statut")
def changer_statut_facture(
facture_id: int,
statut: str = Form(...),
date_paiement: str = Form(""),
db: Session = Depends(get_db)
):
facture = db.query(Facture).get(facture_id)
if not facture:
raise HTTPException(status_code=404)
facture.statut = StatutFacture(statut)
if statut == "payee" and date_paiement:
facture.date_paiement = date.fromisoformat(date_paiement)
db.commit()
return RedirectResponse(f"/factures/{facture_id}", status_code=303)
@router.get("/{facture_id}/pdf")
def telecharger_pdf(facture_id: int, db: Session = Depends(get_db)):
from weasyprint import HTML
facture = db.query(Facture).get(facture_id)
if not facture:
raise HTTPException(status_code=404)
html_content = templates.get_template("pdf/facture.html").render({
"facture": facture,
"settings": settings,
})
pdf_bytes = HTML(string=html_content, base_url=".").write_pdf()
filename = f"facture-{facture.numero}.pdf"
return Response(
content=pdf_bytes,
media_type="application/pdf",
headers={"Content-Disposition": f'attachment; filename="{filename}"'}
)
@router.get("/{facture_id}/apercu-pdf", response_class=HTMLResponse)
def apercu_pdf(request: Request, facture_id: int, db: Session = Depends(get_db)):
facture = db.query(Facture).get(facture_id)
if not facture:
raise HTTPException(status_code=404)
return render(templates, "pdf/facture.html", request, {
"facture": facture,
"settings": settings,
})