feat: étend le référentiel au parc entier et à la suppression des lots
Trois manques que la saisie a fait apparaître : Le tableau porte une colonne immeuble, la liste ne peut donc plus être suspendue à un immeuble choisi d'avance : elle renvoie tout le parc, chaque ligne emportant de quoi nommer son immeuble sans requête de plus. Un lot qu'aucune ligne de compte rendu ne mentionne ne décrit rien : il encombre la saisie et doit pouvoir disparaître. La garde est côté serveur — un lot porteur de revenus ou de dépenses est refusé, ses montants partiraient avec lui. Le nom d'usage s'enregistre, et la réponse recalcule les compteurs de l'immeuble plutôt que de les laisser à zéro : elle remplace l'immeuble dans les listes du client, qui le croirait vide de lots. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,12 @@ PDF, la vider a moitie, ou la laisser contredire l'extraction en silence.
|
||||
|
||||
import pytest
|
||||
|
||||
from plesna_gerance.database.models import Depense, Immeuble, Lot
|
||||
from plesna_gerance.database.models import (
|
||||
Depense,
|
||||
Immeuble,
|
||||
Lot,
|
||||
LotCaracteristiques,
|
||||
)
|
||||
from plesna_gerance.database.service import DatabaseService
|
||||
|
||||
|
||||
@@ -41,7 +46,7 @@ def test_liste_les_lots_sans_fiche(api_client, immeuble_et_lot):
|
||||
"""Un lot jamais decrit doit apparaitre, sinon il n'est pas saisissable."""
|
||||
immeuble, lot = immeuble_et_lot
|
||||
|
||||
response = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel")
|
||||
response = api_client.get(f"/api/lots/referentiel?immeuble_id={immeuble.id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
lignes = response.json()
|
||||
@@ -55,7 +60,36 @@ def test_liste_les_lots_sans_fiche(api_client, immeuble_et_lot):
|
||||
|
||||
|
||||
def test_liste_un_immeuble_inconnu_donne_404(api_client):
|
||||
assert api_client.get("/api/immeubles/99999/lots/referentiel").status_code == 404
|
||||
assert api_client.get("/api/lots/referentiel?immeuble_id=99999").status_code == 404
|
||||
|
||||
|
||||
def test_liste_tout_le_parc_par_defaut(api_client, immeuble_et_lot, db_session):
|
||||
"""Le tableau porte une colonne immeuble : il les montre donc tous.
|
||||
|
||||
Restreindre a un immeuble par defaut obligerait a le choisir avant de voir
|
||||
quoi que ce soit, alors que le parc entier tient dans un ecran.
|
||||
"""
|
||||
autre = Immeuble(code="IMM2", denomination="Marietton")
|
||||
db_session.add(autre)
|
||||
db_session.flush()
|
||||
db_session.add(Lot(immeuble_id=autre.id, numero="01"))
|
||||
db_session.commit()
|
||||
|
||||
lignes = api_client.get("/api/lots/referentiel").json()
|
||||
|
||||
assert len(lignes) == 2
|
||||
assert {ligne["immeuble_code"] for ligne in lignes} == {"IMM1", "IMM2"}
|
||||
|
||||
|
||||
def test_expose_le_nom_d_usage_de_l_immeuble(api_client, immeuble_et_lot):
|
||||
"""Chaque ligne porte de quoi nommer son immeuble sans requete de plus."""
|
||||
immeuble, _ = immeuble_et_lot
|
||||
api_client.put(f"/api/immeubles/{immeuble.id}", json={"denomination": "Servient"})
|
||||
|
||||
ligne = api_client.get("/api/lots/referentiel").json()[0]
|
||||
|
||||
assert ligne["immeuble_denomination"] == "Servient"
|
||||
assert ligne["immeuble_code"] == "IMM1"
|
||||
|
||||
|
||||
def test_enregistre_puis_relit_une_fiche(api_client, immeuble_et_lot):
|
||||
@@ -69,7 +103,7 @@ def test_enregistre_puis_relit_une_fiche(api_client, immeuble_et_lot):
|
||||
assert fiche["etage"] == "RC"
|
||||
assert fiche["numero_fiscal"] == "690123456789"
|
||||
|
||||
relu = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel").json()[0]
|
||||
relu = api_client.get(f"/api/lots/referentiel?immeuble_id={immeuble.id}").json()[0]
|
||||
assert relu["caracteristiques"]["surface"] == 148.0
|
||||
assert relu["caracteristiques"]["dpe_classe"] == "C"
|
||||
|
||||
@@ -94,7 +128,7 @@ def test_une_seconde_ecriture_met_a_jour_sans_dupliquer(api_client, immeuble_et_
|
||||
f"/api/lots/{lot.id}/caracteristiques", json={**FICHE, "surface": 150.0}
|
||||
)
|
||||
|
||||
lignes = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel").json()
|
||||
lignes = api_client.get(f"/api/lots/referentiel?immeuble_id={immeuble.id}").json()
|
||||
assert len(lignes) == 1
|
||||
assert lignes[0]["caracteristiques"]["surface"] == 150.0
|
||||
|
||||
@@ -175,7 +209,7 @@ def test_une_re_extraction_ne_touche_pas_la_fiche(
|
||||
|
||||
DatabaseService(db_session).save_document(data=sample_data, overwrite=True)
|
||||
|
||||
lignes = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel").json()
|
||||
lignes = api_client.get(f"/api/lots/referentiel?immeuble_id={immeuble.id}").json()
|
||||
fiche = next(ligne for ligne in lignes if ligne["id"] == lot.id)["caracteristiques"]
|
||||
assert fiche is not None, "la re-extraction a perdu la fiche du lot"
|
||||
assert fiche["surface"] == 148.0
|
||||
@@ -185,7 +219,7 @@ def test_compte_les_rattachements_du_lot(api_client, immeuble_et_lot):
|
||||
"""Un lot avec des revenus n'est pas un orphelin : la liste doit le dire."""
|
||||
immeuble, lot = immeuble_et_lot
|
||||
|
||||
ligne = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel").json()[0]
|
||||
ligne = api_client.get(f"/api/lots/referentiel?immeuble_id={immeuble.id}").json()[0]
|
||||
|
||||
assert ligne["nb_revenus"] == 1
|
||||
# La depense de la fixture porte sur l'immeuble (lot_id NULL). La compter
|
||||
@@ -193,6 +227,21 @@ def test_compte_les_rattachements_du_lot(api_client, immeuble_et_lot):
|
||||
assert ligne["nb_depenses"] == 0
|
||||
|
||||
|
||||
def test_l_enregistrement_renvoie_les_memes_comptes_que_la_liste(
|
||||
api_client, immeuble_et_lot
|
||||
):
|
||||
"""Décrire un lot occupé ne doit pas le faire passer pour inutilisé.
|
||||
|
||||
La réponse du PUT remplace la ligne dans le tableau : si elle rapporte zéro
|
||||
revenu, le lot se pare d'un « inutilisé » que la liste dément au rechargement.
|
||||
"""
|
||||
_, lot = immeuble_et_lot
|
||||
|
||||
ligne = api_client.put(f"/api/lots/{lot.id}/caracteristiques", json=FICHE).json()
|
||||
|
||||
assert ligne["nb_revenus"] == 1
|
||||
|
||||
|
||||
def test_compte_les_depenses_propres_au_lot(api_client, immeuble_et_lot, db_session):
|
||||
"""Une depense rattachee au lot, elle, doit bien remonter sur sa ligne."""
|
||||
immeuble, lot = immeuble_et_lot
|
||||
@@ -200,6 +249,112 @@ def test_compte_les_depenses_propres_au_lot(api_client, immeuble_et_lot, db_sess
|
||||
depense.lot_id = lot.id
|
||||
db_session.commit()
|
||||
|
||||
ligne = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel").json()[0]
|
||||
ligne = api_client.get(f"/api/lots/referentiel?immeuble_id={immeuble.id}").json()[0]
|
||||
|
||||
assert ligne["nb_depenses"] == 1
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Suppression des lots sans rattachement
|
||||
# ============================================================
|
||||
|
||||
|
||||
def test_supprime_un_lot_inutilise(api_client, immeuble_et_lot, db_session):
|
||||
"""Les lots d'un ancien format de numerotation doivent pouvoir disparaitre."""
|
||||
immeuble, _ = immeuble_et_lot
|
||||
orphelin = Lot(immeuble_id=immeuble.id, numero="0001", type="Appartement T1")
|
||||
db_session.add(orphelin)
|
||||
db_session.commit()
|
||||
|
||||
response = api_client.delete(f"/api/lots/{orphelin.id}")
|
||||
|
||||
assert response.status_code == 204
|
||||
numeros = [
|
||||
ligne["numero"]
|
||||
for ligne in api_client.get(
|
||||
f"/api/lots/referentiel?immeuble_id={immeuble.id}"
|
||||
).json()
|
||||
]
|
||||
assert numeros == ["01"]
|
||||
|
||||
|
||||
def test_supprimer_un_lot_emporte_sa_fiche(api_client, immeuble_et_lot, db_session):
|
||||
"""Sans cascade, la fiche resterait en base sans lot pour la porter."""
|
||||
immeuble, _ = immeuble_et_lot
|
||||
orphelin = Lot(immeuble_id=immeuble.id, numero="0001")
|
||||
db_session.add(orphelin)
|
||||
db_session.commit()
|
||||
api_client.put(f"/api/lots/{orphelin.id}/caracteristiques", json=FICHE)
|
||||
|
||||
api_client.delete(f"/api/lots/{orphelin.id}")
|
||||
|
||||
assert db_session.query(LotCaracteristiques).count() == 0
|
||||
|
||||
|
||||
def test_refuse_de_supprimer_un_lot_avec_des_revenus(api_client, immeuble_et_lot):
|
||||
"""Le refus protege des montants : les perdre fausserait les comptes."""
|
||||
_, lot = immeuble_et_lot
|
||||
|
||||
response = api_client.delete(f"/api/lots/{lot.id}")
|
||||
|
||||
assert response.status_code == 409
|
||||
assert "revenu" in response.json()["detail"]
|
||||
|
||||
|
||||
def test_refuse_de_supprimer_un_lot_avec_des_depenses(
|
||||
api_client, immeuble_et_lot, db_session
|
||||
):
|
||||
"""Une depense seule suffit a retenir le lot, meme sans aucun revenu."""
|
||||
immeuble, _ = immeuble_et_lot
|
||||
lot_charge = Lot(immeuble_id=immeuble.id, numero="0002")
|
||||
db_session.add(lot_charge)
|
||||
db_session.commit()
|
||||
depense = db_session.query(Depense).one()
|
||||
depense.lot_id = lot_charge.id
|
||||
db_session.commit()
|
||||
|
||||
response = api_client.delete(f"/api/lots/{lot_charge.id}")
|
||||
|
||||
assert response.status_code == 409
|
||||
|
||||
|
||||
def test_supprimer_un_lot_inconnu_donne_404(api_client):
|
||||
assert api_client.delete("/api/lots/99999").status_code == 404
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Nom d'usage de l'immeuble
|
||||
# ============================================================
|
||||
|
||||
|
||||
def test_nomme_un_immeuble(api_client, immeuble_et_lot):
|
||||
immeuble, _ = immeuble_et_lot
|
||||
|
||||
response = api_client.put(
|
||||
f"/api/immeubles/{immeuble.id}", json={"denomination": "Servient"}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["denomination"] == "Servient"
|
||||
assert api_client.get("/api/immeubles").json()[0]["denomination"] == "Servient"
|
||||
# La reponse remplace l'immeuble dans les listes du client : des compteurs a
|
||||
# zero le feraient passer pour un immeuble sans lot.
|
||||
assert response.json()["nb_lots"] == 1
|
||||
|
||||
|
||||
def test_effacer_le_nom_rend_l_immeuble_a_son_code(api_client, immeuble_et_lot):
|
||||
immeuble, _ = immeuble_et_lot
|
||||
api_client.put(f"/api/immeubles/{immeuble.id}", json={"denomination": "Servient"})
|
||||
|
||||
response = api_client.put(
|
||||
f"/api/immeubles/{immeuble.id}", json={"denomination": " "}
|
||||
)
|
||||
|
||||
assert response.json()["denomination"] is None
|
||||
assert response.json()["code"] == "IMM1"
|
||||
|
||||
|
||||
def test_nommer_un_immeuble_inconnu_donne_404(api_client):
|
||||
response = api_client.put("/api/immeubles/99999", json={"denomination": "Servient"})
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
Reference in New Issue
Block a user