fix: distingue un lot reloué d'un lot sorti de la gestion

La fiche du lot 15 annonçait « dernier loyer connu, arrêté après févr. 26 »
alors qu'un locataire y est entré le 8 juillet — et la courbe juste dessous,
où la barre de juillet est bien là, la contredisait.

Le loyer en vigueur se lit sur les paliers, et un mois facturé au seul
prorata n'en ouvre pas : celui d'avant s'arrête donc à la dernière mensualité
pleine, quatre mois avant le nouveau bail. Ne rien lire après lui confond deux
états que le compte rendu sépare : un lot qui n'est plus loué, et un lot entre
deux locataires.

La distinction tient à la frontière qui sert déjà à séparer loyer plein et
prorata, la période portée par le compte rendu :

- un prorata qui finit le dernier jour du mois sans partir du premier facture
  la fin du mois : quelqu'un est entré, le bail court encore après ;
- un prorata qui part du premier sans l'atteindre facture le début : le bail
  s'arrête là ;
- un montant négatif ne fait ni l'un ni l'autre. Un avoir porte parfois la
  période exacte de la ligne qu'il annule, et le lire comme une entrée
  inventerait une relocation là où rien n'a été loué.

L'entrée l'emporte sur la sortie parce qu'elle vient après : ce lot voit son
locataire partir le 10 mars et le suivant arriver le 8 juillet, et c'est le
second qui décrit son état.

Le montant affiché ne bouge pas — le nouveau bail n'a été facturé qu'au
prorata, et en tirer un loyer mensuel l'inventerait. Seul ce qu'on en dit
change : « dernier loyer plein · nouveau bail depuis juil. 26 ».

Sans relocation, la sortie date enfin la fin de la location au mois où elle a
lieu : « arrêté en cours de mars 26 » plutôt qu'« arrêté après févr. 26 », qui
l'avançait d'un mois.

Sur le parc : 31 lots loués, 1 relouté, 1 réellement arrêté.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 20:50:54 +02:00
parent faab7173bd
commit b15f5c95b6
5 changed files with 319 additions and 6 deletions

View File

@@ -9,6 +9,7 @@ regrouper des lignes que le compte rendu a émises séparément.
import pytest
from plesna_gerance.database.models import (
Document,
Immeuble,
Locataire,
Lot,
@@ -616,3 +617,151 @@ def test_la_comparaison_compte_les_lots_qu_elle_ne_peut_pas_voir(
assert parc["mediane_immeuble"] is None
assert parc["nb_immeuble"] == 0
assert parc["sans_surface"] == 0
@pytest.fixture
def bail_qui_change(db_session, sample_data):
"""Un lot dont le bail s'arrête en cours de mois, puis reprend au suivant.
C'est la chronologie du lot 15 du parc : deux mois pleins, une sortie le
10 mars, quatre mois vides, puis une entrée le 8 juillet facturée au seul
prorata. Le dernier compte rendu ne porte donc aucun loyer plein, alors que
le lot est bel et bien reloué.
"""
def compte_rendu(reference, date_cr, lignes):
return {
**sample_data,
"metadata": {
**sample_data["metadata"],
"document": {
"reference": reference,
"date": date_cr,
"type": "COMPTE RENDU DE GESTION",
},
},
"situation_locataires": [
{
"lot": {"numero": "01", "type": "Appartement"},
"locataire": {"nom": lignes["nom"]},
"lignes": [
{
"type": "loyer",
"periode": {
"debut": lignes["debut"],
"fin": lignes["fin"],
},
"loyers": lignes["loyers"],
"total": lignes["loyers"],
"regles": lignes["loyers"],
"impayes": 0.0,
}
],
}
],
"recapitulatif_operations": [],
}
service = DatabaseService(db_session)
for reference, date_cr, lignes in [
(
"CR01",
"2026-01-26",
{
"nom": "SORTANT",
"debut": "2026-01-01",
"fin": "2026-01-31",
"loyers": 1447.05,
},
),
(
"CR02",
"2026-02-26",
{
"nom": "SORTANT",
"debut": "2026-02-01",
"fin": "2026-02-28",
"loyers": 1447.05,
},
),
(
"CR03",
"2026-03-23",
{
"nom": "SORTANT",
"debut": "2026-03-01",
"fin": "2026-03-10",
"loyers": 482.35,
},
),
(
"CR04",
"2026-07-28",
{
"nom": "ENTRANT",
"debut": "2026-07-08",
"fin": "2026-07-31",
"loyers": 1111.67,
},
),
]:
service.save_document(data=compte_rendu(reference, date_cr, lignes))
immeuble = db_session.query(Immeuble).filter(Immeuble.code == "IMM1").one()
lot = db_session.query(Lot).filter(Lot.immeuble_id == immeuble.id).one()
return lot
def test_un_lot_reloue_au_prorata_n_est_pas_un_lot_arrete(api_client, bail_qui_change):
"""Le palier s'arrête en février, le lot est reloué en juillet.
Sans cette lecture, la fiche annonce « arrêté après févr. » sur un lot qui
vient de retrouver un locataire — et la courbe juste dessous, où la barre
de juillet est bien là, la contredit.
"""
vigueur = api_client.get(f"/api/lots/{bail_qui_change.id}/analyse").json()["loyer"][
"en_vigueur"
]
assert vigueur["reloue_depuis"] == "2026-07"
assert vigueur["sortie_en"] is None
# Le montant reste celui du bail précédent : le nouveau n'a été facturé
# qu'au prorata, et en tirer un loyer mensuel l'inventerait.
assert vigueur["mois"] == "2026-02"
assert vigueur["loyer"] == 1447.05
assert vigueur["toujours_loue"] is False
def test_une_sortie_en_cours_de_mois_deborde_le_dernier_palier(
api_client, db_session, bail_qui_change
):
"""Sans relocation, c'est la sortie qui date la fin de la location.
Le dernier loyer plein est celui de février, mais le lot est resté loué
jusqu'au 10 mars : dire « arrêté après févr. » avancerait la sortie d'un
mois.
"""
dernier = (
db_session.query(Document).order_by(Document.date.desc()).first()
)
db_session.delete(dernier)
db_session.commit()
vigueur = api_client.get(f"/api/lots/{bail_qui_change.id}/analyse").json()["loyer"][
"en_vigueur"
]
assert vigueur["sortie_en"] == "2026-03"
assert vigueur["reloue_depuis"] is None
def test_un_loyer_qui_s_arrete_net_reste_un_loyer_arrete(api_client, donnees):
"""Aucun prorata après le dernier palier : rien ne nuance l'arrêt."""
_, lot = donnees
vigueur = api_client.get(f"/api/lots/{lot.id}/analyse").json()["loyer"][
"en_vigueur"
]
assert vigueur["reloue_depuis"] is None
assert vigueur["sortie_en"] is None