Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Csrd etape collecte donnees #195

Merged
merged 15 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions impact/impact/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@
"data:", # some images are defined this way
# matomo :
"stats.beta.gouv.fr",
# S3 scaleway :
"sites-faciles.s3.fr-par.scw.cloud",
],
"style-src": [
SELF,
Expand Down
5 changes: 5 additions & 0 deletions impact/reglementations/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ class EtapeCSRD:
"selection-enjeux",
"analyse-materialite",
"collection-donnees-entreprise",
"redaction-rapport-durabilite",
]

@classmethod
Expand Down Expand Up @@ -433,4 +434,8 @@ def get(cls, id_etape):
id="collection-donnees-entreprise",
nom="Collecter les données de son entreprise",
),
EtapeCSRD(
id="redaction-rapport-durabilite",
nom="Rédiger son rapport de durabilité",
),
]
6 changes: 5 additions & 1 deletion impact/reglementations/forms/csrd.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ def __init__(self, *args, esrs: str = None, **kwargs):
if self.instance:
qs = self.instance.enjeux_par_esrs(self.esrs)
self.fields["enjeux"].queryset = qs
self.initial = {"enjeux": qs.filter(selection=True)}
self.initial = {"enjeux": qs.selectionnes()}

def save(self, *args, **kwargs):
super().save(*args, **kwargs)

for enjeu in self.instance.enjeux.filter(esrs=self.esrs):
enjeu.selection = enjeu in self.cleaned_data["enjeux"]
if not enjeu.selection:
# si un enjeu n'est pas sélectionné, il ne peut pas être analysé
# (raz éventuelle de l'analyse si on déselectionne l'enjeu)
enjeu.materiel = None
enjeu.save()

def sections(self):
Expand Down
9 changes: 6 additions & 3 deletions impact/reglementations/models/csrd.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,16 @@ def modifiables(self):
return self.filter(modifiable=True)

def materiels(self):
return self.filter(materiel=True)
return self.selectionnes().filter(materiel=True)

def non_materiels(self):
return self.filter(materiel=False)
return self.selectionnes().filter(materiel=False)

def analyses(self):
return self.selectionnes().filter(materiel__isnull=False)

def non_analyses(self):
return self.filter(materiel__isnull=True)
return self.selectionnes().filter(materiel__isnull=True)

def environnement(self):
return self.filter(esrs__startswith="ESRS_E")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,49 @@

<div class="fr-col">
<h2>{{ etape.nom }}</h2>
<p>
Étape en préparation : vous pourrez bientôt télécharger vos données matérielles et non-matérielles.
</p>
<h3>Analyse de la matérialité des informations élémentaires (points de données ou « data points »)</h3>

<p>La phase 1 "analyse de double matérialité" vous a permis de déterminer vos enjeux de durabilité matériels. Cette phase 2 vous permet de déterminer les indicateurs pertinents sur lesquels vous allez reporter, puis collecter les données exigées.</p>

<p>La matérialité des informations s’appréhende en fonction des critères suivants : (i) l’importance de l’information élémentaire pour décrire l’enjeu ou (ii) son utilité pour répondre aux besoins des utilisateurs.</p>


<div class="fr-container">
<div class="fr-grid-row fr-grid-row--middle">
<div class="fr-col-8">
<h6>Que faire ?</h6>
<ul class="csrd-list csrd-list--bold">
<li>Téléchargez les exigences de publications des ESRS de vos enjeux ESG prioritaires, puis sélectionnez les points de données à reporter.
<div>
<a class="fr-link fr-link--download" download
href="{% url 'reglementations:datapoints_xlsx' csrd.entreprise.siren %}">Exigences de publication matérielles</a>
</div>
</li>
<li>Complétez, si pertinent, votre analyse avec les exigences de publication des autres ESRS.
<div>
<a class="fr-link fr-link--download" download
href="{% url 'reglementations:datapoints_xlsx' csrd.entreprise.siren %}?materiel=false">Exigences de publication non-matérielles</a>
</div>
</li>
</ul>

</div>
</div>
</div>

<p>Pour vous aidez, vous pouvez consulter le logigramme permettant de déterminer les informations à inclure au titre des ESRS.</p>

<div class="fr-accordions-group fr-mb-4w">
<section class="fr-accordion">
<h3 class="fr-accordion__title">
<button class="fr-accordion__btn" aria-expanded="false" aria-controls="accordion-114">Consulter le logigramme</button>
</h3>
<div class="fr-collapse" id="accordion-114">
<img class="fr-responsive-img" src="https://sites-faciles.s3.fr-par.scw.cloud/sites-faciles/images/processus-decision-point-de-donnee.original.svg" alt="logigramme">
</div>
</section>
</div>

<p>
<a href="{{ sites_faciles_base_url }}/realiser-le-rapport-de-durabilite/collecter-les-donnees-de-son-entreprise/" target="_blank">En savoir plus sur les démarches de collecte des données</a>
</p>
Expand All @@ -29,4 +69,7 @@ <h2>{{ etape.nom }}</h2>

</div>

<p>
{% include "snippets/csrd_submit.html" with prochaine_etape="redaction-rapport-durabilite" %}
</p>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% extends "base.html" %}

{% block title %}Collection des données de l'entreprise{% endblock %}

{% block content %}

{% include "snippets/csrd_header.html" %}

<div class="fr-container fr-mb-8w">
<div class="fr-grid-row fr-pb-6w">
{% include "snippets/gestion_csrd_menu.html" %}

<div class="fr-col">
<h2>{{ etape.nom }}</h2>
<p>
Étape en préparation : vous pourrez bientôt enregistrer le lien de téléchargement de votre Rapport de Durabilité.
</p>
</div>

</div>

{% endblock %}
149 changes: 142 additions & 7 deletions impact/reglementations/tests/test_csrd_views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from datetime import date
from datetime import datetime
from io import BytesIO

import pytest
from django.contrib.messages import WARNING
from django.urls import reverse
from openpyxl import load_workbook
from pytest_django.asserts import assertTemplateUsed

from habilitations.models import attach_user_to_entreprise
Expand Down Expand Up @@ -128,6 +130,8 @@ def test_guide_de_la_csrd_par_etape(etape, client, alice, entreprise_factory):
"/csrd/{siren}/etape-introduction",
"/csrd/{siren}/etape-selection-enjeux",
"/csrd/{siren}/etape-analyse-materialite",
"/csrd/{siren}/etape-collection-donnees-entreprise",
"/csrd/{siren}/etape-redaction-rapport-durabilite",
],
)
def test_gestion_de_la_csrd(etape, client, alice, entreprise_factory):
Expand Down Expand Up @@ -155,22 +159,38 @@ def test_gestion_de_la_csrd(etape, client, alice, entreprise_factory):
assertTemplateUsed(
response, "reglementations/csrd/etape-analyse-materialite.html"
)
elif etape.endswith("collection-donnees-entreprise"):
assertTemplateUsed(
response, "reglementations/csrd/etape-collection-donnees-entreprise.html"
)
elif etape.endswith("redaction-rapport-durabilite"):
assertTemplateUsed(
response, "reglementations/csrd/etape-redaction-rapport-durabilite.html"
)

rapport_csrd = RapportCSRD.objects.get(proprietaire=alice, entreprise=entreprise)
NOMBRE_ENJEUX = 103
assert len(rapport_csrd.enjeux.all()) == NOMBRE_ENJEUX


def test_étape_inexistante_de_la_csrd(client, alice, entreprise_factory):
entreprise = entreprise_factory()
attach_user_to_entreprise(alice, entreprise, "Présidente")
client.force_login(alice)
etape_inexistante = f"/csrd/{entreprise.siren}/etape-4"

response = client.get(etape_inexistante)

assert response.status_code == 404

rapport_csrd = RapportCSRD.objects.get(proprietaire=alice, entreprise=entreprise)
NOMBRE_ENJEUX = 103
assert len(rapport_csrd.enjeux.all()) == NOMBRE_ENJEUX


@pytest.mark.parametrize(
"etape",
[
"introduction",
"selection-enjeux",
"analyse-materialite",
"collection-donnees-entreprise",
],
)
def test_enregistrement_de_l_étape_de_la_csrd(etape, client, alice, entreprise_factory):
Expand All @@ -188,9 +208,6 @@ def test_enregistrement_de_l_étape_de_la_csrd(etape, client, alice, entreprise_

response = client.post(url, follow=True)

content = response.content.decode("utf-8")
assert "<!-- page gestion CSRD -->" in content

rapport_csrd = RapportCSRD.objects.get(proprietaire=alice, entreprise=entreprise)
assert rapport_csrd.etape_validee == etape

Expand All @@ -200,6 +217,8 @@ def test_enregistrement_de_l_étape_de_la_csrd(etape, client, alice, entreprise_
[
"introduction",
"selection-enjeux",
"analyse-materialite",
"collection-donnees-entreprise",
],
)
def test_enregistrement_de_l_étape_de_la_csrd_retourne_une_404_si_aucune_CSRD(
Expand Down Expand Up @@ -377,3 +396,119 @@ def test_liste_des_enjeux_csrd(client, alice, entreprise_non_qualifiee):
assert "<!-- fragment liste des enjeux sélectionnés -->" in response.content.decode(
"utf-8"
)


def test_datapoints_pour_enjeux_materiels_au_format_xlsx(
client, alice, entreprise_non_qualifiee
):
attach_user_to_entreprise(alice, entreprise_non_qualifiee, "Présidente")
csrd = RapportCSRD.objects.create(
proprietaire=alice,
entreprise=entreprise_non_qualifiee,
annee=f"{datetime.now():%Y}",
)
enjeux = csrd.enjeux.all()
enjeu_attenuation = enjeux[1]
enjeu_attenuation.selection = True
enjeu_attenuation.materiel = True
enjeu_attenuation.save()
esrs_materielle = enjeu_attenuation.esrs
client.force_login(alice)

response = client.get(
f"/csrd/{entreprise_non_qualifiee.siren}/datapoints.xlsx",
)

assert (
response["content-type"]
== "application/vnd.openxmlformatsofficedocument.spreadsheetml.sheet"
)
workbook = load_workbook(filename=BytesIO(response.content))
noms_onglet = workbook.get_sheet_names()
assert esrs_materielle.replace("_", " ") in noms_onglet
assert "Index" in noms_onglet
assert "ESRS 2" in noms_onglet
assert "ESRS2 MDR" in noms_onglet
assert "ESRS G1" not in noms_onglet


def test_datapoints_pour_enjeux_non_materiels_au_format_xlsx(
client, alice, entreprise_non_qualifiee
):
attach_user_to_entreprise(alice, entreprise_non_qualifiee, "Présidente")
csrd = RapportCSRD.objects.create(
proprietaire=alice,
entreprise=entreprise_non_qualifiee,
annee=f"{datetime.now():%Y}",
)
enjeux = csrd.enjeux.all()

# enjeu de l'ESRS_E1:
enjeu_attenuation = enjeux[1]
enjeu_attenuation.selection = True
enjeu_attenuation.materiel = True
enjeu_attenuation.save()
esrs_materielle = enjeu_attenuation.esrs
client.force_login(alice)

# note : les enjeux affichés dans le fichier "non-matériels"
# doivent au préalable avoir été sélectionnés

# enjeu de l'ESRS_G1:
enjeux_G1 = enjeux.filter(esrs="ESRS_G1").first()
enjeux_G1.selection = True
enjeux_G1.materiel = False # et pas None
enjeux_G1.save()

response = client.get(
f"/csrd/{entreprise_non_qualifiee.siren}/datapoints.xlsx?materiel=false",
)

assert (
response["content-type"]
== "application/vnd.openxmlformatsofficedocument.spreadsheetml.sheet"
)
workbook = load_workbook(filename=BytesIO(response.content))
noms_onglet = workbook.sheetnames
assert esrs_materielle.replace("_", " ") not in noms_onglet
assert "Index" in noms_onglet
assert "ESRS 2" in noms_onglet
assert "ESRS2 MDR" in noms_onglet
assert "ESRS G1" in noms_onglet


def test_datapoints_csrd__au_format_xlsx_retourne_une_404_si_entreprise_inexistante(
client, alice
):
client.force_login(alice)

response = client.get(
f"/csrd/000000001/datapoints.xlsx",
)

assert response.status_code == 404


def test_datapoints_csrd_au_format_xlsx_retourne_une_404_si_habilitation_inexistante(
client, alice, entreprise_non_qualifiee
):
client.force_login(alice)

response = client.get(
f"/csrd/{entreprise_non_qualifiee.siren}/datapoints.xlsx",
)

assert response.status_code == 404


def test_datapoints_csrd_au_format_xlsx_retourne_une_404_si_csrd_inexistante(
client, alice, entreprise_non_qualifiee
):
attach_user_to_entreprise(alice, entreprise_non_qualifiee, "Présidente")
client.force_login(alice)

response = client.get(
f"/csrd/{entreprise_non_qualifiee.siren}/datapoints.xlsx",
)

assert response.status_code == 404
5 changes: 5 additions & 0 deletions impact/reglementations/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@
views.csrd.enjeux_materiels_xlsx,
name="enjeux_materiels_xlsx",
),
path(
"csrd/<str:siren>/datapoints.xlsx",
views.csrd.datapoints_xlsx,
name="datapoints_xlsx",
),
]

# Fragments HTMX
Expand Down
Loading
Loading