"""Tests de la distinction flux / stock dans les agregats de revenus. Chaque compte rendu reporte la dette du precedent dans une ligne `solde_anterieur`. Cumuler ces lignes recompte la meme creance a chaque document et, surtout, empeche un remboursement de s'inscrire : une dette soldee resterait affichee a vie. Le decor rejoue le cycle observe en production : un locataire laisse un impaye, le compte rendu suivant le reporte, il le solde, puis le reporte disparait. """ import copy from datetime import date, timedelta import pytest from plesna_gerance.database.service import DatabaseService def _mois_glissant(recul: int) -> str: jour = date.today().replace(day=15) for _ in range(recul): jour = (jour.replace(day=1) - timedelta(days=1)).replace(day=15) return jour.isoformat() def _ligne(type_ligne: str, **montants) -> dict: base = { "type": type_ligne, "periode": {"debut": None, "fin": None}, "loyers": 0.0, "taxes": 0.0, "provisions": 0.0, "total": 0.0, "regles": 0.0, "impayes": 0.0, } base.update(montants) return base def _compte_rendu(sample_data: dict, reference: str, recul: int, lignes: list) -> dict: data = copy.deepcopy(sample_data) data["metadata"]["document"]["reference"] = reference data["metadata"]["document"]["date"] = _mois_glissant(recul) data["situation_locataires"][0]["lignes"] = lignes data["recapitulatif_operations"] = [] return data @pytest.fixture def dette_reportee_puis_soldee(db_session, sample_data): """Trois mois : un impaye nait, il est reporte, il est solde. Mois 1 : loyer de 800 dont 300 impayes. Mois 2 : les 300 sont reportes et regles ; loyer de 800 regle en entier. Mois 3 : plus aucun report ; loyer de 800 regle en entier. """ service = DatabaseService(db_session) service.save_document( data=_compte_rendu( sample_data, "M1", 2, [_ligne("loyer", loyers=800.0, total=800.0, regles=500.0, impayes=300.0)], ) ) service.save_document( data=_compte_rendu( sample_data, "M2", 1, [ _ligne("solde_anterieur", loyers=300.0, total=300.0, regles=300.0), _ligne("loyer", loyers=800.0, total=800.0, regles=800.0), ], ) ) service.save_document( data=_compte_rendu( sample_data, "M3", 0, [_ligne("loyer", loyers=800.0, total=800.0, regles=800.0)], ) ) return db_session def test_le_restant_du_est_celui_du_dernier_compte_rendu( api_client, dette_reportee_puis_soldee ): """La dette soldee disparait : le cumul afficherait encore 300.""" kpis = api_client.get("/api/revenus/summary").json()["kpis"] assert kpis["total_impayes"] == 0.0 def test_le_facture_ignore_le_report(api_client, dette_reportee_puis_soldee): """3 loyers de 800 : le report de 300 n'est pas un revenu de plus.""" kpis = api_client.get("/api/revenus/summary").json()["kpis"] assert kpis["total_revenus"] == 2400.0 def test_l_encaisse_retient_le_reglement_d_une_vieille_dette( api_client, dette_reportee_puis_soldee ): """Les 300 regles sur la ligne de report sont de l'argent bien recu. C'est ce qui interdit d'ecarter la ligne de report en bloc : sa colonne `total` est un stock deja compte, mais sa colonne `regles` est un flux. """ kpis = api_client.get("/api/revenus/summary").json()["kpis"] assert kpis["total_regles"] == 500.0 + 300.0 + 800.0 + 800.0 def test_le_taux_compare_un_perimetre_homogene( api_client, dette_reportee_puis_soldee ): """Regle sur facture, report exclu des deux cotes : 2100 / 2400. Rapporter l'encaisse (2400, rattrapage compris) au facture ferait afficher un taux de 100 % alors qu'un impaye est ne sur la periode. """ kpis = api_client.get("/api/revenus/summary").json()["kpis"] assert kpis["taux_recouvrement"] == round(2100.0 / 2400.0 * 100, 1) def test_le_classement_des_impayes_oublie_qui_a_paye( api_client, dette_reportee_puis_soldee ): """Un locataire a jour ne doit plus figurer parmi les debiteurs.""" top = api_client.get("/api/revenus/summary").json()["top_impayes"] assert top == [] def test_les_mois_restent_comparables(api_client, dette_reportee_puis_soldee): """Chaque mois vaut son loyer : le report ne gonfle pas le mois 2.""" by_month = api_client.get("/api/revenus/summary").json()["by_month"] assert [point["total"] for point in by_month] == [800.0, 800.0, 800.0] # L'impaye reste le solde constate ce mois-la, pas un cumul. assert [point["impayes"] for point in by_month] == [300.0, 0.0, 0.0]