fix: aligne le dashboard sur la règle flux / stock
All checks were successful
Build and Publish Docker Image / Build App Image (push) Successful in 22s
Build and Publish Docker Image / Build Summary (push) Successful in 3s

L'accueil et la page Revenus calculaient leurs totaux chacun de leur
côté : le premier lisait le dernier compte rendu, la seconde cumulait
tout. Les deux écrans affichaient donc deux impayés différents pour la
même notion — 49 374 € contre 247 354 €.

Les raccourcis immeubles cumulaient la dette, et les courbes mensuelles
réintégraient le report chaque mois, ce qui rendait deux mois
incomparables. Tous passent par les mêmes règles que la page Revenus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 10:50:15 +02:00
parent 3e2d103929
commit 11bd3ca539
2 changed files with 158 additions and 46 deletions

View File

@@ -17,6 +17,7 @@ from ...database.models import (
Lot,
Revenu,
)
from ...services.revenus_query import est_flux, flux_par, restant_du_par
router = APIRouter(prefix="/api/dashboard", tags=["dashboard"])
@@ -114,10 +115,13 @@ async def get_financial_summary(
last_document_date = str(last_doc.date)
last_document_reference = last_doc.reference
# Revenus du dernier document
# Revenus du dernier document : le solde reporte du mois precedent n'est
# pas un revenu du mois, il est deja compte dans les impayes.
revenus = (
session.execute(
select(func.sum(Revenu.total)).where(Revenu.document_id == last_doc.id)
select(func.sum(Revenu.total))
.where(Revenu.document_id == last_doc.id)
.where(est_flux())
).scalar()
or 0.0
)
@@ -153,11 +157,13 @@ async def get_financial_summary(
impayes_by_month: dict[str, float] = defaultdict(float)
depenses_by_month: dict[str, float] = defaultdict(float)
# Recuperer revenus et impayes par mois
# Recuperer revenus et impayes par mois. Le revenu du mois exclut le report
# pour que les mois soient comparables ; l'impaye reste le solde constate ce
# mois-la, de sorte que la courbe suive la dette au lieu de l'empiler.
revenus_stmt = (
select(
Document.date,
func.sum(Revenu.total).label("total"),
func.sum(Revenu.total).filter(est_flux()).label("total"),
func.sum(Revenu.impayes).label("impayes"),
)
.join(Revenu, Revenu.document_id == Document.id)
@@ -277,9 +283,10 @@ async def get_monthly_trends(
today = date.today()
start_date = (today.replace(day=1) - timedelta(days=months * 31)).replace(day=1)
# Recuperer tous les revenus depuis start_date
# Recuperer tous les revenus depuis start_date, report exclu : la courbe
# compare des mois entre eux, pas des soldes cumules.
revenus_stmt = (
select(Document.date, func.sum(Revenu.total).label("total"))
select(Document.date, func.sum(Revenu.total).filter(est_flux()).label("total"))
.join(Revenu, Revenu.document_id == Document.id)
.where(Document.date >= start_date)
.group_by(Document.date)
@@ -333,51 +340,55 @@ async def get_immeubles_shortcuts(
- **limit**: Nombre maximum d'immeubles (defaut: 5)
"""
# Requete pour les immeubles avec stats
# Effectifs et activite, puis flux et restant du : trois granularites
# differentes, jointes plutot que melangees pour ne pas se multiplier.
effectifs = (
select(
Lot.immeuble_id.label("immeuble_id"),
func.count(func.distinct(Lot.id)).label("nb_lots"),
func.count(func.distinct(Locataire.id)).label("nb_locataires"),
)
.outerjoin(Locataire, Locataire.lot_id == Lot.id)
.group_by(Lot.immeuble_id)
.subquery()
)
activite = (
select(
Document.immeuble_id.label("immeuble_id"),
func.count(Document.id).label("nb_documents"),
)
.group_by(Document.immeuble_id)
.subquery()
)
flux = flux_par(Document.immeuble_id)
stock = restant_du_par(Document.immeuble_id)
stmt = (
select(
Immeuble,
func.count(func.distinct(Lot.id)).label("nb_lots"),
func.count(func.distinct(Locataire.id)).label("nb_locataires"),
func.count(func.distinct(Document.id)).label("nb_documents"),
effectifs.c.nb_lots,
effectifs.c.nb_locataires,
flux.c.facture,
stock.c.restant_du,
)
.outerjoin(Lot, Lot.immeuble_id == Immeuble.id)
.outerjoin(Locataire, Locataire.lot_id == Lot.id)
.outerjoin(Document, Document.immeuble_id == Immeuble.id)
.group_by(Immeuble.id)
.order_by(desc("nb_documents"))
.outerjoin(effectifs, effectifs.c.immeuble_id == Immeuble.id)
.outerjoin(activite, activite.c.immeuble_id == Immeuble.id)
.outerjoin(flux, flux.c.cle == Immeuble.id)
.outerjoin(stock, stock.c.cle == Immeuble.id)
.order_by(desc(activite.c.nb_documents))
.limit(limit)
)
result = session.execute(stmt)
immeubles = result.all()
# Pour chaque immeuble, recuperer les totaux revenus/impayes
shortcuts = []
for row in immeubles:
immeuble = row.Immeuble
# Revenus de cet immeuble
revenus_stmt = (
select(func.sum(Revenu.total), func.sum(Revenu.impayes))
.join(Lot, Revenu.lot_id == Lot.id)
.where(Lot.immeuble_id == immeuble.id)
return [
ImmeubleShortcutResponse(
id=row.Immeuble.id,
code=row.Immeuble.code,
adresse=row.Immeuble.adresse,
ville=row.Immeuble.ville,
nb_lots=row.nb_lots or 0,
nb_locataires=row.nb_locataires or 0,
total_revenus=row.facture or 0.0,
total_impayes=row.restant_du or 0.0,
)
rev_result = session.execute(revenus_stmt).first()
total_revenus = rev_result[0] or 0.0 if rev_result else 0.0
total_impayes = rev_result[1] or 0.0 if rev_result else 0.0
shortcuts.append(
ImmeubleShortcutResponse(
id=immeuble.id,
code=immeuble.code,
adresse=immeuble.adresse,
ville=immeuble.ville,
nb_lots=row.nb_lots or 0,
nb_locataires=row.nb_locataires or 0,
total_revenus=total_revenus,
total_impayes=total_impayes,
)
)
return shortcuts
for row in session.execute(stmt)
]