refactor: supprime le code mort et unifie les routes de tags

Code sans aucun appelant, retiré : DatabaseService.get_revenus_summary
(annoté list[dict] alors qu'il retournait un dict), get_depenses_summary,
get_tag_by_id et storage.file_exists.

Les tags étaient gérés à deux adresses : /api/tags (lecture, appelée par
trois composants) et /api/config/tags (lecture, création, renommage,
appelée par la seule page de configuration). Tout est regroupé sur
/api/tags, dans le module qui leur est dédié ; config.py ne garde que les
settings et ConfigPage est recâblée. Ces routes n'avaient aucun test :
sept en couvrent maintenant la création, le renommage, l'unicité et les
noms vides.

/api/stats disparaît également : sous-ensemble de /api/dashboard/stats,
il n'était appelé par personne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 09:44:28 +02:00
parent ea5bdac18a
commit 49e060e6e4
7 changed files with 122 additions and 177 deletions

View File

@@ -4,8 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy.orm import Session
from ...database import DatabaseService, get_session
from ...database.models import Tag
from ...database import get_session
from ...services.settings_service import (
SETTINGS_REGISTRY,
delete_setting,
@@ -25,14 +24,6 @@ class SettingUpdate(BaseModel):
value: str
class TagCreate(BaseModel):
nom: str
class TagUpdate(BaseModel):
nom: str
# ============================================================
# Settings endpoints
# ============================================================
@@ -71,64 +62,3 @@ async def reset_setting(
# Return the resolved value after deletion
all_settings = get_all_settings(session)
return all_settings[key]
# ============================================================
# Tags endpoints
# ============================================================
@router.get("/tags")
async def list_tags(
session: Session = Depends(get_session),
) -> list[dict]:
"""Liste tous les tags."""
db_service = DatabaseService(session)
tags = db_service.list_tags()
return [{"id": tag.id, "nom": tag.nom} for tag in tags]
@router.post("/tags", status_code=201)
async def create_tag(
body: TagCreate,
session: Session = Depends(get_session),
) -> dict:
"""Crée un nouveau tag (validation unicité)."""
nom = body.nom.strip()
if not nom:
raise HTTPException(status_code=400, detail="Le nom du tag ne peut pas être vide.")
existing = session.query(Tag).filter(Tag.nom == nom).first()
if existing:
raise HTTPException(status_code=409, detail=f"Le tag '{nom}' existe déjà.")
tag = Tag(nom=nom)
session.add(tag)
session.commit()
session.refresh(tag)
return {"id": tag.id, "nom": tag.nom}
@router.put("/tags/{tag_id}")
async def rename_tag(
tag_id: int,
body: TagUpdate,
session: Session = Depends(get_session),
) -> dict:
"""Renomme un tag (validation unicité)."""
nom = body.nom.strip()
if not nom:
raise HTTPException(status_code=400, detail="Le nom du tag ne peut pas être vide.")
tag = session.query(Tag).filter(Tag.id == tag_id).first()
if not tag:
raise HTTPException(status_code=404, detail="Tag introuvable.")
existing = session.query(Tag).filter(Tag.nom == nom, Tag.id != tag_id).first()
if existing:
raise HTTPException(status_code=409, detail=f"Le tag '{nom}' existe déjà.")
tag.nom = nom
session.commit()
session.refresh(tag)
return {"id": tag.id, "nom": tag.nom}

View File

@@ -6,11 +6,9 @@ from datetime import datetime
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
from fastapi.responses import JSONResponse, Response
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from ...database import DatabaseService, get_session, storage
from ...database.models import Depense, Document, Immeuble, Locataire, Lot, Revenu
from ...database.service import DuplicateDocumentError
from ...extractor import extract_compte_rendu
from ...utils.canonical import canonical_copy
@@ -149,24 +147,6 @@ async def save_document_with_pdf(
)
@router.get("/stats")
async def get_stats(
session: Session = Depends(get_session),
) -> dict:
"""Retourne les statistiques globales de la base de donnees.
Compteurs pour chaque table principale.
"""
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,
}
@router.get("/documents", response_model=list[DocumentSummary])
async def list_documents(
limit: int = 100,

View File

@@ -1,15 +1,34 @@
"""Tags routes - Tag management and prediction."""
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy.orm import Session
from ...database import DatabaseService, get_session
from ...database.models import Tag
from ...services.tag_predictor import TagPredictor
from ..schemas import PredictTagsRequest
router = APIRouter(prefix="/api", tags=["tags"])
class TagBody(BaseModel):
"""Corps de requete pour creer ou renommer un tag."""
nom: str
def _tag_dict(tag: Tag) -> dict:
return {"id": tag.id, "nom": tag.nom}
def _nom_valide(nom: str) -> str:
nom = nom.strip()
if not nom:
raise HTTPException(status_code=400, detail="Le nom du tag ne peut pas être vide.")
return nom
@router.get("/tags")
async def list_tags(
session: Session = Depends(get_session),
@@ -21,7 +40,47 @@ async def list_tags(
db_service = DatabaseService(session)
tags = db_service.list_tags()
return [{"id": tag.id, "nom": tag.nom} for tag in tags]
return [_tag_dict(tag) for tag in tags]
@router.post("/tags", status_code=201)
async def create_tag(
body: TagBody,
session: Session = Depends(get_session),
) -> dict:
"""Cree un nouveau tag (validation unicite)."""
nom = _nom_valide(body.nom)
if session.query(Tag).filter(Tag.nom == nom).first():
raise HTTPException(status_code=409, detail=f"Le tag '{nom}' existe déjà.")
tag = Tag(nom=nom)
session.add(tag)
session.commit()
session.refresh(tag)
return _tag_dict(tag)
@router.put("/tags/{tag_id}")
async def rename_tag(
tag_id: int,
body: TagBody,
session: Session = Depends(get_session),
) -> dict:
"""Renomme un tag (validation unicite)."""
nom = _nom_valide(body.nom)
tag = session.query(Tag).filter(Tag.id == tag_id).first()
if not tag:
raise HTTPException(status_code=404, detail="Tag introuvable.")
if session.query(Tag).filter(Tag.nom == nom, Tag.id != tag_id).first():
raise HTTPException(status_code=409, detail=f"Le tag '{nom}' existe déjà.")
tag.nom = nom
session.commit()
session.refresh(tag)
return _tag_dict(tag)
@router.post("/predict-tags")