feat: add homepage

This commit is contained in:
2026-01-18 21:11:35 +01:00
parent ee1db93298
commit c5e51a7513
17 changed files with 1550 additions and 115 deletions

View File

@@ -2,13 +2,18 @@
import tempfile
from pathlib import Path
from typing import Any
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi import FastAPI, File, HTTPException, UploadFile, Depends
from fastapi.responses import JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from sqlalchemy.orm import Session
from .. import __version__
from ..extractor import extract_compte_rendu
from ..database import init_db, get_session, DatabaseService
from ..database.service import DuplicateDocumentError
app = FastAPI(
title="Plesna Gérance API",
@@ -20,6 +25,46 @@ app = FastAPI(
)
# Initialize database on startup
@app.on_event("startup")
async def startup_event():
"""Initialize database on application startup."""
init_db()
# Pydantic models for API
class SaveRequest(BaseModel):
"""Request body for saving extracted data."""
source_file: str | None = None
data: dict[str, Any]
class SaveResponse(BaseModel):
"""Response for save operation."""
success: bool
message: str
document_id: int | None = None
reference: str | None = None
date: str | None = None
class DocumentSummary(BaseModel):
"""Summary of a saved document."""
id: int
reference: str
date: str
type: str | None
source_file: str | None
immeuble_code: str | None
immeuble_adresse: str | None
solde_montant: float | None
solde_type: str | None
created_at: str
# Determine the frontend dist path
FRONTEND_DIST = Path(__file__).parent.parent.parent.parent / "frontend" / "dist"
@@ -105,6 +150,184 @@ async def extract_pdf(
tmp_path.unlink()
@app.post("/api/save", tags=["database"], response_model=SaveResponse)
async def save_document(
request: SaveRequest,
session: Session = Depends(get_session),
) -> SaveResponse:
"""Sauvegarde les donnees extraites en base de donnees.
- **source_file**: Nom du fichier PDF original (optionnel)
- **data**: Donnees extraites (format identique a la reponse de /api/extract)
Retourne un message de succes avec l'ID du document cree,
ou une erreur si le document existe deja (doublon).
"""
try:
db_service = DatabaseService(session)
document = db_service.save_document(
data=request.data, source_file=request.source_file
)
return SaveResponse(
success=True,
message="Document sauvegarde avec succes",
document_id=document.id,
reference=document.reference,
date=str(document.date),
)
except DuplicateDocumentError as e:
return SaveResponse(
success=False,
message=f"Document deja existant: reference={e.reference}, date={e.date}",
reference=e.reference,
date=str(e.date),
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Erreur lors de la sauvegarde: {str(e)}"
)
@app.get("/api/stats", tags=["database"])
async def get_stats(
session: Session = Depends(get_session),
) -> dict:
"""Retourne les statistiques globales de la base de donnees.
Compteurs pour chaque table principale.
"""
from sqlalchemy import func, select
from ..database.models import Document, Immeuble, Lot, Locataire, Revenu, Depense
return {
"documents": session.execute(select(func.count(Document.id))).scalar() or 0,
"immeubles": session.execute(select(func.count(Immeuble.id))).scalar() or 0,
"lots": session.execute(select(func.count(Lot.id))).scalar() or 0,
"locataires": session.execute(select(func.count(Locataire.id))).scalar() or 0,
"revenus": session.execute(select(func.count(Revenu.id))).scalar() or 0,
"depenses": session.execute(select(func.count(Depense.id))).scalar() or 0,
}
@app.get("/api/documents", tags=["database"])
async def list_documents(
limit: int = 100,
offset: int = 0,
session: Session = Depends(get_session),
) -> list[DocumentSummary]:
"""Liste les documents importes en base.
- **limit**: Nombre maximum de documents a retourner (defaut: 100)
- **offset**: Decalage pour la pagination (defaut: 0)
Retourne une liste de documents avec leurs metadonnees principales.
"""
db_service = DatabaseService(session)
documents = db_service.list_documents(limit=limit, offset=offset)
return [
DocumentSummary(
id=doc.id,
reference=doc.reference,
date=str(doc.date),
type=doc.type,
source_file=doc.source_file,
immeuble_code=doc.immeuble.code if doc.immeuble else None,
immeuble_adresse=doc.immeuble.adresse if doc.immeuble else None,
solde_montant=doc.solde_montant,
solde_type=doc.solde_type,
created_at=doc.created_at.isoformat() if doc.created_at else None,
)
for doc in documents
]
@app.get("/api/documents/{document_id}", tags=["database"])
async def get_document(
document_id: int,
session: Session = Depends(get_session),
) -> dict:
"""Recupere un document par son ID avec toutes ses donnees.
Retourne le document complet incluant le JSON original.
"""
db_service = DatabaseService(session)
document = db_service.get_document_by_id(document_id)
if not document:
raise HTTPException(status_code=404, detail="Document non trouve")
import json
return {
"id": document.id,
"reference": document.reference,
"date": str(document.date),
"type": document.type,
"source_file": document.source_file,
"immeuble": {
"code": document.immeuble.code,
"adresse": document.immeuble.adresse,
"ville": document.immeuble.ville,
"code_postal": document.immeuble.code_postal,
}
if document.immeuble
else None,
"solde": {
"montant": document.solde_montant,
"type": document.solde_type,
"date_arrete": str(document.solde_date_arrete)
if document.solde_date_arrete
else None,
},
"editeur": {
"nom": document.editeur_nom,
"siret": document.editeur_siret,
},
"json_data": json.loads(document.json_data) if document.json_data else None,
"created_at": document.created_at.isoformat() if document.created_at else None,
}
@app.get("/api/check-duplicate", tags=["database"])
async def check_duplicate(
reference: str,
date: str,
session: Session = Depends(get_session),
) -> dict:
"""Verifie si un document existe deja en base.
- **reference**: Reference du document
- **date**: Date du document (format YYYY-MM-DD)
Retourne {exists: true/false, document_id: ...}
"""
from datetime import datetime
try:
doc_date = datetime.strptime(date, "%Y-%m-%d").date()
except ValueError:
raise HTTPException(
status_code=400, detail="Format de date invalide. Utiliser YYYY-MM-DD"
)
db_service = DatabaseService(session)
existing = db_service.check_duplicate(reference, doc_date)
return {
"exists": existing is not None,
"document_id": existing.id if existing else None,
"reference": reference,
"date": date,
}
# Mount static files for production (if dist exists)
if FRONTEND_DIST.exists():
# Serve static assets