Skip to content

Commit

Permalink
[WIP Tests] Update avenants logements (#1712)
Browse files Browse the repository at this point in the history
  • Loading branch information
syldb authored Jan 28, 2025
1 parent 8e0672b commit 8601571
Show file tree
Hide file tree
Showing 27 changed files with 1,457 additions and 348 deletions.
4 changes: 2 additions & 2 deletions apilos_settings/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class Commune(models.Model):
commune = models.CharField(max_length=255, blank=True)
# Needed to import xlsx files
import_mapping = {
"Code postal": code_postal,
"Commune": commune,
"Code postal": "code_postal",
"Commune": "commune",
}
sheet_name: str = "Communes"

Expand Down
141 changes: 120 additions & 21 deletions conventions/forms/convention_form_logements.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ class LotLgtsOptionForm(forms.Form):
"required": "Le nombre de logements est obligatoire",
},
)
formset_sans_loyer_disabled = forms.BooleanField(
required=False,
)
formset_disabled = forms.BooleanField(
required=False,
)
formset_corrigee_disabled = forms.BooleanField(
required=False,
)
formset_corrigee_sans_loyer_disabled = forms.BooleanField(
required=False,
)


class LotFoyerResidenceLgtsDetailsForm(forms.Form):
Expand Down Expand Up @@ -129,12 +141,7 @@ def clean_surface_habitable_totale(self):
return surface_habitable_totale


class LogementForm(forms.Form):
"""
Formulaire Logement formant la liste des logements d'une convention de type HLM,
SEM, type I & 2 : une ligne du tableau des logements
"""

class BaseLogementForm(forms.Form):
uuid = forms.UUIDField(
required=False,
label="Logement",
Expand Down Expand Up @@ -166,6 +173,91 @@ class LogementForm(forms.Form):
"max_digits": "La surface habitable doit-être inférieur à 10000 m²",
},
)
import_order = forms.IntegerField(
label="",
required=False,
)


class LogementCorrigeeSansLoyerForm(BaseLogementForm):
surface_corrigee = forms.DecimalField(
label="",
max_digits=6,
decimal_places=2,
error_messages={
"required": "La surface corrigée est obligatoire",
"max_digits": "La surface corrigée doit-être inférieur à 10000 m²",
},
)


class LogementCorrigeeForm(LogementCorrigeeSansLoyerForm):
loyer_par_metre_carre = forms.DecimalField(
label="",
max_digits=6,
decimal_places=2,
error_messages={
"required": "Le loyer par m² est obligatoire",
"max_digits": "La loyer par m² doit-être inférieur à 10000 €",
},
)
coeficient = forms.DecimalField(
required=True,
label="",
max_digits=6,
decimal_places=4,
error_messages={
"required": "Le coefficient est obligatoire",
"max_digits": "La coefficient doit-être inférieur à 1000",
},
)
loyer = forms.DecimalField(
required=True,
label="",
max_digits=6,
decimal_places=2,
error_messages={
"required": "Le loyer est obligatoire",
"max_digits": "La loyer doit-être inférieur à 10000 €",
},
)

def clean_loyer(self):
"""
Vérifcations:
- le loyer doit-être le produit de la surface utile, du loyer par mètre carré et
du coefficient (tolérance de 1 €)
"""
surface_corrigee = self.cleaned_data.get("surface_corrigee", 0)
loyer_par_metre_carre = self.cleaned_data.get("loyer_par_metre_carre", 0)
coeficient = self.cleaned_data.get("coeficient", 0)
loyer = self.cleaned_data.get("loyer", 0)

if (
abs(
round_half_up(loyer, 2)
- round_half_up(
surface_corrigee * loyer_par_metre_carre * coeficient, 2
)
)
> 1
):
raise ValidationError(
"Le loyer doit-être le produit de la surface corrigée,"
+ " du loyer par mètre carré et du coefficient. valeur attendue :"
+ f" {round_half_up(surface_corrigee*loyer_par_metre_carre*coeficient,2)} €"
+ " (tolérance de 1 €)"
)

return loyer


class LogementSansLoyerForm(BaseLogementForm):
"""
Formulaire Logement formant la liste des logements d'une convention de type HLM,
SEM, type I & 2 : une ligne du tableau des logements par surface réelle sans loyers
"""

surface_annexes = forms.DecimalField(
label="",
max_digits=6,
Expand Down Expand Up @@ -193,6 +285,14 @@ class LogementForm(forms.Form):
"max_digits": "La surface utile doit-être inférieur à 10000 m²",
},
)


class LogementForm(LogementSansLoyerForm):
"""
Formulaire Logement formant la liste des logements d'une convention de type HLM,
SEM, type I & 2 : une ligne du tableau des logements
"""

loyer_par_metre_carre = forms.DecimalField(
label="",
max_digits=6,
Expand All @@ -203,6 +303,7 @@ class LogementForm(forms.Form):
},
)
coeficient = forms.DecimalField(
required=True,
label="",
max_digits=6,
decimal_places=4,
Expand All @@ -212,6 +313,7 @@ class LogementForm(forms.Form):
},
)
loyer = forms.DecimalField(
required=True,
label="",
max_digits=6,
decimal_places=2,
Expand All @@ -220,10 +322,6 @@ class LogementForm(forms.Form):
"max_digits": "La loyer doit-être inférieur à 10000 €",
},
)
import_order = forms.IntegerField(
label="",
required=False,
)

def clean_loyer(self):
"""
Expand Down Expand Up @@ -264,6 +362,7 @@ class BaseLogementFormSet(BaseFormSet):
programme_id: int = None
lot_id: int = None
nb_logements: int = None
total_nb_logements: int = None
optional_errors: list = []
ignore_optional_errors = False

Expand All @@ -279,17 +378,8 @@ def clean(self):
if self.ignore_optional_errors:
return
self.optional_errors = []
self.manage_non_empty_validation()
self.manage_nb_logement_consistency()

def manage_non_empty_validation(self):
"""
Validation: la liste des logements ne peut pas être vide
"""
if len(self.forms) == 0:
error = ValidationError("La liste des logements ne peut pas être vide")
self.optional_errors.append(error)

def manage_designation_validation(self):
"""
Validation: les designations de logement doivent être uniques par convention
Expand Down Expand Up @@ -376,10 +466,10 @@ def manage_nb_logement_consistency(self):
Validation: le nombre de logements déclarés pour cette convention à l'étape Opération
doit correspondre au nombre de logements de la liste à l'étape Logements
"""
if self.nb_logements != self.total_form_count():
if self.nb_logements != self.total_nb_logements:
error = ValidationError(
f"Le nombre de logement à conventionner ({self.nb_logements}) "
+ f"ne correspond pas au nombre de logements déclaré ({self.total_form_count()})"
+ f"ne correspond pas au nombre de logements déclaré ({self.total_nb_logements})"
)
self.optional_errors.append(error)

Expand Down Expand Up @@ -413,6 +503,15 @@ def manage_coefficient_propre(self):


LogementFormSet = formset_factory(LogementForm, formset=BaseLogementFormSet, extra=0)
LogementSansLoyerFormSet = formset_factory(
LogementSansLoyerForm, formset=BaseLogementFormSet, extra=0
)
LogementCorrigeeFormSet = formset_factory(
LogementCorrigeeForm, formset=BaseLogementFormSet, extra=0
)
LogementCorrigeeSansLoyerFormSet = formset_factory(
LogementCorrigeeSansLoyerForm, formset=BaseLogementFormSet, extra=0
)


class FoyerResidenceLogementForm(forms.Form):
Expand Down
12 changes: 6 additions & 6 deletions conventions/models/pret.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ class Pret(models.Model):

# Needed to import xlsx files
import_mapping = {
"Numéro\n(caractères alphanuméric)": numero,
"Date d'octroi\n(format dd/mm/yyyy)": date_octroi,
"Durée\n(en années)": duree,
"Montant\n(en €)": montant,
"Prêteur\n(choisir dans la liste déroulante)": preteur,
"Préciser l'identité du préteur si vous avez sélectionné 'Autre'": autre,
"Numéro\n(caractères alphanuméric)": "numero",
"Date d'octroi\n(format dd/mm/yyyy)": "date_octroi",
"Durée\n(en années)": "duree",
"Montant\n(en €)": "montant",
"Prêteur\n(choisir dans la liste déroulante)": "preteur",
"Préciser l'identité du préteur si vous avez sélectionné 'Autre'": "autre",
}
sheet_name = "Financements"

Expand Down
10 changes: 6 additions & 4 deletions conventions/services/convention_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def _compute_total_logement(convention):
"sa_totale": 0,
"sar_totale": 0,
"su_totale": 0,
"sc_totale": 0,
"loyer_total": 0,
}
nb_logements_par_type = {}
Expand All @@ -82,6 +83,7 @@ def _compute_total_logement(convention):
logements_totale["sa_totale"] += logement.surface_annexes or 0
logements_totale["sar_totale"] += logement.surface_annexes_retenue or 0
logements_totale["su_totale"] += logement.surface_utile or 0
logements_totale["sc_totale"] += logement.surface_corrigee or 0
logements_totale["loyer_total"] += logement.loyer or 0
if logement.get_typologie_display() not in nb_logements_par_type:
nb_logements_par_type[logement.get_typologie_display()] = 0
Expand Down Expand Up @@ -140,9 +142,6 @@ def generate_convention_doc(convention: Convention, save_data=False) -> DocxTemp

adresse = _get_adresse(convention)

# Logements should keep the importation order
logements = convention.lot.logements.order_by("import_order")

context = {
**avenant_data,
"convention": convention,
Expand All @@ -152,7 +151,10 @@ def generate_convention_doc(convention: Convention, save_data=False) -> DocxTemp
"lot": convention.lot,
"administration": convention.programme.administration,
"logement_edds": logement_edds,
"logements": logements,
"logements": convention.lot.logements_import_ordered,
"logements_sans_loyer": convention.lot.logements_sans_loyer_import_ordered,
"logements_corrigee": convention.lot.logements_corrigee_import_ordered,
"logements_corrigee_sans_loyer": convention.lot.logements_corrigee_sans_loyer_import_ordered,
"locaux_collectifs": convention.lot.locaux_collectifs.all(),
"annexes": annexes,
"stationnements": convention.lot.type_stationnements.all(),
Expand Down
3 changes: 3 additions & 0 deletions conventions/services/conventions.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class ConventionService(ABC):
editable_after_upload: bool = False
form: Form | None = None
formset = None
formset_sans_loyer = None
formset_corrigee = None
formset_corrigee_sans_loyer = None
upform: Form | None = None
extra_forms: dict[str, Form | None] | None = None

Expand Down
Loading

0 comments on commit 8601571

Please sign in to comment.