From 840b3ce5a34e5a39416828de85397caa5154af78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agn=C3=A8s=20Haasser?= Date: Mon, 28 Mar 2022 15:56:33 +0200 Subject: [PATCH 1/7] Transform one organisationrequest into one organisation --- aidants_connect_habilitation/admin.py | 8 ++ aidants_connect_habilitation/models.py | 74 ++++++++++++++++++- .../tests/test_models.py | 68 ++++++++++++++++- 3 files changed, 147 insertions(+), 3 deletions(-) diff --git a/aidants_connect_habilitation/admin.py b/aidants_connect_habilitation/admin.py index 9418e9426..149a21cf1 100644 --- a/aidants_connect_habilitation/admin.py +++ b/aidants_connect_habilitation/admin.py @@ -60,6 +60,14 @@ class OrganisationRequestAdmin(VisibleToAdminMetier, ReverseModelAdmin): inline_type = "stacked" inline_reverse = ("manager",) + actions = ("accept_request",) + + def accept_request(self, request, queryset): + for organisation_request in queryset: + organisation_request.accept_request_and_create_organisation() + + accept_request.short_description = "Accepter la demande" + if settings.AC_HABILITATION_FORM_ENABLED: admin_site.register(Issuer, IssuerAdmin) diff --git a/aidants_connect_habilitation/models.py b/aidants_connect_habilitation/models.py index 357c309ba..43cbdc16d 100644 --- a/aidants_connect_habilitation/models.py +++ b/aidants_connect_habilitation/models.py @@ -3,7 +3,7 @@ from uuid import uuid4 from django.conf import settings -from django.db import models +from django.db import models, transaction from django.db.models import SET_NULL, Q from django.dispatch import Signal from django.http import HttpRequest @@ -18,7 +18,12 @@ RequestOriginConstants, RequestStatusConstants, ) -from aidants_connect_web.models import OrganisationType +from aidants_connect_web.models import ( + Aidant, + HabilitationRequest, + Organisation, + OrganisationType, +) from aidants_connect_web.utilities import generate_new_datapass_id __all__ = [ @@ -162,6 +167,13 @@ class Meta: class OrganisationRequest(models.Model): + organisation = models.ForeignKey( + Organisation, + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + issuer = models.ForeignKey( Issuer, on_delete=models.CASCADE, @@ -281,6 +293,64 @@ def prepare_request_for_ac_validation(self, form_data: dict): self.data_pass_id = int(f"{self.zipcode[:3]}{generate_new_datapass_id()}") self.save() + @transaction.atomic + def accept_request_and_create_organisation(self): + if self.status != RequestStatusConstants.AC_VALIDATION_PROCESSING.value: + return False + + organisation = Organisation.objects.create( + name=self.name, + type=(self.type_other if self.type_other else self.type), + siret=self.siret, + address=self.address, + zipcode=self.zipcode, + city=self.city, + data_pass_id=self.data_pass_id, + ) + + self.organisation = organisation + self.status = RequestStatusConstants.VALIDATED.value + self.save() + + responsable_query = Aidant.objects.filter(username=self.manager.email) + + if not responsable_query.exists(): + responsable = Aidant.objects.create( + first_name=self.manager.first_name, + last_name=self.manager.last_name, + email=self.manager.email, + phone=self.manager.phone, + profession=self.manager.profession, + organisation=organisation, + can_create_mandats=False, + ) + + responsable.responsable_de.add(organisation) + responsable.save() + else: + responsable = responsable_query[0] + responsable.responsable_de.add(organisation) + responsable.save() + + for aidant in self.aidant_requests.all(): + HabilitationRequest.objects.create( + first_name=aidant.first_name, + last_name=aidant.last_name, + email=aidant.email, + profession=aidant.profession, + organisation=organisation, + ) + if self.manager.is_aidant: + HabilitationRequest.objects.create( + first_name=self.manager.first_name, + last_name=self.manager.last_name, + email=self.manager.email, + profession=self.manager.profession, + organisation=organisation, + ) + + return True + class Meta: constraints = [ models.CheckConstraint( diff --git a/aidants_connect_habilitation/tests/test_models.py b/aidants_connect_habilitation/tests/test_models.py index 852294ef0..303b21d73 100644 --- a/aidants_connect_habilitation/tests/test_models.py +++ b/aidants_connect_habilitation/tests/test_models.py @@ -8,12 +8,17 @@ from freezegun import freeze_time -from aidants_connect.common.constants import RequestOriginConstants +from aidants_connect.common.constants import ( + RequestOriginConstants, + RequestStatusConstants, +) from aidants_connect_habilitation.models import Issuer, IssuerEmailConfirmation from aidants_connect_habilitation.tests.factories import ( + AidantRequestFactory, IssuerFactory, OrganisationRequestFactory, ) +from aidants_connect_web.models import Aidant, HabilitationRequest, Organisation @tag("models") @@ -54,6 +59,67 @@ def test_manager_set_constraint(self): OrganisationRequestFactory(manager=None) self.assertIn("manager_set", str(cm.exception)) + def test_accept_when_all_is_fine(self): + def prepare_data(): + organisation_request = OrganisationRequestFactory( + status=RequestStatusConstants.AC_VALIDATION_PROCESSING.value, + data_pass_id=67245456, + ) + organisation_request.manager.is_aidant = True + organisation_request.manager.save() + for _ in range(3): + AidantRequestFactory(organisation=organisation_request) + organisation_request.save() + return organisation_request + + organisation_request = prepare_data() + result = organisation_request.accept_request_and_create_organisation() + + # verify if organisation was created + self.assertTrue(result, "Result of method call should be True") + self.assertTrue( + Organisation.objects.filter( + data_pass_id=organisation_request.data_pass_id + ).exists(), + "Organisation should have been created", + ) + organisation = Organisation.objects.get( + data_pass_id=organisation_request.data_pass_id + ) + + # verify if organisation was added to organisation_request + self.assertEqual(organisation_request.organisation, organisation) + + # verify if responsable aidant account was properly created and added to org + self.assertEqual( + 1, Aidant.objects.filter(email=organisation_request.manager.email).count() + ) + responsable = Aidant.objects.get(email=organisation_request.manager.email) + + # verify if responsable was added to organisation + self.assertIn(responsable, organisation.responsables.all()) + self.assertFalse(responsable.can_create_mandats) + + # verify status + self.assertEqual( + organisation_request.status, RequestStatusConstants.VALIDATED.value + ) + + # verify if aidants were created + for aidant_request in organisation_request.aidant_requests.all(): + self.assertTrue( + HabilitationRequest.objects.filter( + organisation=organisation, email=aidant_request.email + ).exists(), + f"Habilitation request has not been created for {aidant_request.email}", + ) + # check if responsable is on the list of aidants too + self.assertTrue( + HabilitationRequest.objects.filter( + organisation=organisation, email=organisation_request.manager.email + ).exists() + ) + class TestIssuerEmailConfirmation(TestCase): NOW = now() From 7473e47dac899d2538b11d072ea2e8f83baba3fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agn=C3=A8s=20Haasser?= Date: Thu, 7 Apr 2022 12:42:46 +0200 Subject: [PATCH 2/7] Deal with already existing organisations --- aidants_connect_habilitation/models.py | 25 ++++++++++++++++--------- aidants_connect_web/models.py | 3 +++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/aidants_connect_habilitation/models.py b/aidants_connect_habilitation/models.py index 43cbdc16d..adde12d8e 100644 --- a/aidants_connect_habilitation/models.py +++ b/aidants_connect_habilitation/models.py @@ -5,6 +5,7 @@ from django.conf import settings from django.db import models, transaction from django.db.models import SET_NULL, Q +from django.db.utils import IntegrityError from django.dispatch import Signal from django.http import HttpRequest from django.urls import reverse @@ -298,15 +299,21 @@ def accept_request_and_create_organisation(self): if self.status != RequestStatusConstants.AC_VALIDATION_PROCESSING.value: return False - organisation = Organisation.objects.create( - name=self.name, - type=(self.type_other if self.type_other else self.type), - siret=self.siret, - address=self.address, - zipcode=self.zipcode, - city=self.city, - data_pass_id=self.data_pass_id, - ) + try: + organisation = Organisation.objects.create( + name=self.name, + type=(self.type_other if self.type_other else self.type), + siret=self.siret, + address=self.address, + zipcode=self.zipcode, + city=self.city, + data_pass_id=self.data_pass_id, + ) + except IntegrityError: + raise Organisation.AlreadyExists( + "Il existe déjà une organisation portant le n° datapass" + f"{self.data_pass_id}." + ) self.organisation = organisation self.status = RequestStatusConstants.VALIDATED.value diff --git a/aidants_connect_web/models.py b/aidants_connect_web/models.py index b093015ab..ffd9fce87 100644 --- a/aidants_connect_web/models.py +++ b/aidants_connect_web/models.py @@ -96,6 +96,9 @@ class Organisation(models.Model): def __str__(self): return f"{self.name}" + class AlreadyExists(Exception): + pass + @cached_property def num_active_aidants(self): return self.aidants.active().count() From 4dc8f8977cfa88ff21d303ad1db8697cf4fafbd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agn=C3=A8s=20Haasser?= Date: Thu, 7 Apr 2022 12:43:30 +0200 Subject: [PATCH 3/7] Add test cases for transformation orgrequest -> org --- .../tests/test_models.py | 135 ++++++++++++------ 1 file changed, 92 insertions(+), 43 deletions(-) diff --git a/aidants_connect_habilitation/tests/test_models.py b/aidants_connect_habilitation/tests/test_models.py index 303b21d73..2d2ff1246 100644 --- a/aidants_connect_habilitation/tests/test_models.py +++ b/aidants_connect_habilitation/tests/test_models.py @@ -19,6 +19,7 @@ OrganisationRequestFactory, ) from aidants_connect_web.models import Aidant, HabilitationRequest, Organisation +from aidants_connect_web.tests.factories import AidantFactory, OrganisationFactory @tag("models") @@ -59,8 +60,94 @@ def test_manager_set_constraint(self): OrganisationRequestFactory(manager=None) self.assertIn("manager_set", str(cm.exception)) - def test_accept_when_all_is_fine(self): + def prepare_data_for_nominal_case(self): + organisation_request = OrganisationRequestFactory( + status=RequestStatusConstants.AC_VALIDATION_PROCESSING.value, + data_pass_id=67255555, + ) + organisation_request.manager.is_aidant = True + organisation_request.manager.save() + for _ in range(3): + AidantRequestFactory(organisation=organisation_request) + organisation_request.save() + return organisation_request + + def prepare_data_with_existing_responsable(self): + organisation_request = OrganisationRequestFactory( + status=RequestStatusConstants.AC_VALIDATION_PROCESSING.value, + data_pass_id=69366666, + ) + organisation_request.manager.is_aidant = True + organisation_request.manager.save() + # existing manager + AidantFactory( + email=organisation_request.manager.email, + username=organisation_request.manager.email, + can_create_mandats=False, + ) + for _ in range(3): + AidantRequestFactory(organisation=organisation_request) + organisation_request.save() + return organisation_request + + def test_accept_when_it_should_work_fine(self): + for organisation_request in ( + self.prepare_data_for_nominal_case(), + self.prepare_data_with_existing_responsable(), + ): + result = organisation_request.accept_request_and_create_organisation() + + # verify if organisation was created + self.assertTrue(result, "Result of method call should be True") + self.assertTrue( + Organisation.objects.filter( + data_pass_id=organisation_request.data_pass_id + ).exists(), + "Organisation should have been created", + ) + organisation = Organisation.objects.get( + data_pass_id=organisation_request.data_pass_id + ) + + # verify if organisation was added to organisation_request + self.assertEqual(organisation_request.organisation, organisation) + + # verify if responsable aidant account was properly created and added to org + self.assertEqual( + 1, + Aidant.objects.filter(email=organisation_request.manager.email).count(), + ) + responsable = Aidant.objects.get(email=organisation_request.manager.email) + + # verify if responsable was added to organisation + self.assertIn(responsable, organisation.responsables.all()) + self.assertFalse(responsable.can_create_mandats) + + # verify status + self.assertEqual( + organisation_request.status, RequestStatusConstants.VALIDATED.value + ) + + # verify if aidants were created + for aidant_request in organisation_request.aidant_requests.all(): + self.assertTrue( + HabilitationRequest.objects.filter( + organisation=organisation, email=aidant_request.email + ).exists(), + f"Habilitation request was not created for {aidant_request.email}", + ) + # check if responsable is on the list of aidants too + self.assertTrue( + HabilitationRequest.objects.filter( + organisation=organisation, email=organisation_request.manager.email + ).exists() + ) + + def test_accept_fails_when_organisation_already_exists(self): def prepare_data(): + OrganisationFactory( + data_pass_id=67245456, + ) organisation_request = OrganisationRequestFactory( status=RequestStatusConstants.AC_VALIDATION_PROCESSING.value, data_pass_id=67245456, @@ -73,51 +160,13 @@ def prepare_data(): return organisation_request organisation_request = prepare_data() - result = organisation_request.accept_request_and_create_organisation() - - # verify if organisation was created - self.assertTrue(result, "Result of method call should be True") - self.assertTrue( - Organisation.objects.filter( - data_pass_id=organisation_request.data_pass_id - ).exists(), - "Organisation should have been created", - ) - organisation = Organisation.objects.get( - data_pass_id=organisation_request.data_pass_id - ) - - # verify if organisation was added to organisation_request - self.assertEqual(organisation_request.organisation, organisation) - - # verify if responsable aidant account was properly created and added to org - self.assertEqual( - 1, Aidant.objects.filter(email=organisation_request.manager.email).count() - ) - responsable = Aidant.objects.get(email=organisation_request.manager.email) - - # verify if responsable was added to organisation - self.assertIn(responsable, organisation.responsables.all()) - self.assertFalse(responsable.can_create_mandats) + with self.assertRaises(Organisation.AlreadyExists): + organisation_request.accept_request_and_create_organisation(), # verify status self.assertEqual( - organisation_request.status, RequestStatusConstants.VALIDATED.value - ) - - # verify if aidants were created - for aidant_request in organisation_request.aidant_requests.all(): - self.assertTrue( - HabilitationRequest.objects.filter( - organisation=organisation, email=aidant_request.email - ).exists(), - f"Habilitation request has not been created for {aidant_request.email}", - ) - # check if responsable is on the list of aidants too - self.assertTrue( - HabilitationRequest.objects.filter( - organisation=organisation, email=organisation_request.manager.email - ).exists() + organisation_request.status, + RequestStatusConstants.AC_VALIDATION_PROCESSING.value, ) From 05ad09d3bce3fd246c9ee69a863de81636f639f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agn=C3=A8s=20Haasser?= Date: Thu, 7 Apr 2022 12:43:58 +0200 Subject: [PATCH 4/7] Add migration --- .../0018_organisationrequest_organisation.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 aidants_connect_habilitation/migrations/0018_organisationrequest_organisation.py diff --git a/aidants_connect_habilitation/migrations/0018_organisationrequest_organisation.py b/aidants_connect_habilitation/migrations/0018_organisationrequest_organisation.py new file mode 100644 index 000000000..b8fe9c485 --- /dev/null +++ b/aidants_connect_habilitation/migrations/0018_organisationrequest_organisation.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.12 on 2022-04-07 08:34 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('aidants_connect_web', '0085_auto_20220329_0005'), + ('aidants_connect_habilitation', '0017_auto_20220405_1655'), + ] + + operations = [ + migrations.AddField( + model_name='organisationrequest', + name='organisation', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='aidants_connect_web.organisation'), + ), + ] From ccf4a39ad776d3f0db6d37e95cebb49f27bc3a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agn=C3=A8s=20Haasser?= Date: Thu, 7 Apr 2022 13:04:59 +0200 Subject: [PATCH 5/7] Fix issues with status names/labels/values --- aidants_connect_habilitation/models.py | 4 ++-- aidants_connect_habilitation/tests/test_models.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aidants_connect_habilitation/models.py b/aidants_connect_habilitation/models.py index adde12d8e..60d6ca9e4 100644 --- a/aidants_connect_habilitation/models.py +++ b/aidants_connect_habilitation/models.py @@ -296,7 +296,7 @@ def prepare_request_for_ac_validation(self, form_data: dict): @transaction.atomic def accept_request_and_create_organisation(self): - if self.status != RequestStatusConstants.AC_VALIDATION_PROCESSING.value: + if self.status != RequestStatusConstants.AC_VALIDATION_PROCESSING.name: return False try: @@ -316,7 +316,7 @@ def accept_request_and_create_organisation(self): ) self.organisation = organisation - self.status = RequestStatusConstants.VALIDATED.value + self.status = RequestStatusConstants.VALIDATED.name self.save() responsable_query = Aidant.objects.filter(username=self.manager.email) diff --git a/aidants_connect_habilitation/tests/test_models.py b/aidants_connect_habilitation/tests/test_models.py index 2d2ff1246..1000db440 100644 --- a/aidants_connect_habilitation/tests/test_models.py +++ b/aidants_connect_habilitation/tests/test_models.py @@ -62,7 +62,7 @@ def test_manager_set_constraint(self): def prepare_data_for_nominal_case(self): organisation_request = OrganisationRequestFactory( - status=RequestStatusConstants.AC_VALIDATION_PROCESSING.value, + status=RequestStatusConstants.AC_VALIDATION_PROCESSING.name, data_pass_id=67255555, ) organisation_request.manager.is_aidant = True @@ -74,7 +74,7 @@ def prepare_data_for_nominal_case(self): def prepare_data_with_existing_responsable(self): organisation_request = OrganisationRequestFactory( - status=RequestStatusConstants.AC_VALIDATION_PROCESSING.value, + status=RequestStatusConstants.AC_VALIDATION_PROCESSING.name, data_pass_id=69366666, ) organisation_request.manager.is_aidant = True @@ -125,7 +125,7 @@ def test_accept_when_it_should_work_fine(self): # verify status self.assertEqual( - organisation_request.status, RequestStatusConstants.VALIDATED.value + organisation_request.status, RequestStatusConstants.VALIDATED.name ) # verify if aidants were created @@ -149,7 +149,7 @@ def prepare_data(): data_pass_id=67245456, ) organisation_request = OrganisationRequestFactory( - status=RequestStatusConstants.AC_VALIDATION_PROCESSING.value, + status=RequestStatusConstants.AC_VALIDATION_PROCESSING.name, data_pass_id=67245456, ) organisation_request.manager.is_aidant = True @@ -166,7 +166,7 @@ def prepare_data(): # verify status self.assertEqual( organisation_request.status, - RequestStatusConstants.AC_VALIDATION_PROCESSING.value, + RequestStatusConstants.AC_VALIDATION_PROCESSING.name, ) From e2a02c513cea1a811cd32206f667cc104da3bb12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agn=C3=A8s=20Haasser?= Date: Thu, 7 Apr 2022 13:08:30 +0200 Subject: [PATCH 6/7] Add user-friendly error messages --- aidants_connect_habilitation/admin.py | 29 ++++++++++++++++++++++---- aidants_connect_habilitation/models.py | 2 +- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/aidants_connect_habilitation/admin.py b/aidants_connect_habilitation/admin.py index 149a21cf1..3b99560c0 100644 --- a/aidants_connect_habilitation/admin.py +++ b/aidants_connect_habilitation/admin.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.contrib import messages from django.contrib.admin import ModelAdmin, StackedInline, TabularInline from django_reverse_admin import ReverseModelAdmin @@ -15,6 +16,7 @@ OrganisationRequest, RequestMessage, ) +from aidants_connect_web.models import Organisation class OrganisationRequestInline(VisibleToAdminMetier, TabularInline): @@ -50,7 +52,8 @@ class MessageInline(VisibleToAdminMetier, StackedInline): class OrganisationRequestAdmin(VisibleToAdminMetier, ReverseModelAdmin): list_filter = ("status", RegionFilter, DepartmentFilter) - list_display = ("name", "issuer", "status") + list_display = ("name", "issuer", "status", "data_pass_id") + search_fields = ("data_pass_id", "name") raw_id_fields = ("issuer",) readonly_fields = ("public_service_delegation_attestation", "uuid") inlines = ( @@ -63,10 +66,28 @@ class OrganisationRequestAdmin(VisibleToAdminMetier, ReverseModelAdmin): actions = ("accept_request",) def accept_request(self, request, queryset): + orgs_created = 0 for organisation_request in queryset: - organisation_request.accept_request_and_create_organisation() - - accept_request.short_description = "Accepter la demande" + try: + if organisation_request.accept_request_and_create_organisation(): + orgs_created += 1 + except Organisation.AlreadyExists as e: + self.message_user(request, e, level=messages.ERROR) + if orgs_created > 1: + self.message_user( + request, + f"{orgs_created} organisations ont été créées.", + level=messages.SUCCESS, + ) + elif orgs_created == 1: + self.message_user( + request, "Une organisation a été créée.", level=messages.SUCCESS + ) + + accept_request.short_description = ( + "Accepter les demandes sélectionnées " + "(créer les organisations et les aidants à former)" + ) if settings.AC_HABILITATION_FORM_ENABLED: diff --git a/aidants_connect_habilitation/models.py b/aidants_connect_habilitation/models.py index 60d6ca9e4..ca223667c 100644 --- a/aidants_connect_habilitation/models.py +++ b/aidants_connect_habilitation/models.py @@ -311,7 +311,7 @@ def accept_request_and_create_organisation(self): ) except IntegrityError: raise Organisation.AlreadyExists( - "Il existe déjà une organisation portant le n° datapass" + "Il existe déjà une organisation portant le n° datapass " f"{self.data_pass_id}." ) From cdb22d28522ab3b4ed43fbaf078e248401931dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agn=C3=A8s=20Haasser?= Date: Thu, 7 Apr 2022 13:11:33 +0200 Subject: [PATCH 7/7] Add link from OrganisationRequest to Organisation --- aidants_connect_habilitation/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aidants_connect_habilitation/admin.py b/aidants_connect_habilitation/admin.py index 3b99560c0..87e56c596 100644 --- a/aidants_connect_habilitation/admin.py +++ b/aidants_connect_habilitation/admin.py @@ -54,8 +54,8 @@ class OrganisationRequestAdmin(VisibleToAdminMetier, ReverseModelAdmin): list_filter = ("status", RegionFilter, DepartmentFilter) list_display = ("name", "issuer", "status", "data_pass_id") search_fields = ("data_pass_id", "name") - raw_id_fields = ("issuer",) - readonly_fields = ("public_service_delegation_attestation", "uuid") + raw_id_fields = ("issuer", "organisation") + readonly_fields = ("public_service_delegation_attestation", "uuid", "organisation") inlines = ( AidantRequestInline, MessageInline,