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

feat: Ajout d'attributs de contact pour les acheteurs inscrits #1468

Merged
merged 6 commits into from
Nov 7, 2024
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
68 changes: 60 additions & 8 deletions lemarche/api/tenders/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from unittest.mock import patch

from django.conf import settings
from django.test import TestCase
from django.urls import reverse

Expand Down Expand Up @@ -44,11 +47,29 @@ class TenderCreateApiTest(TestCase):
def setUpTestData(cls):
cls.url = reverse("api:tenders-list") + "?token=admin"
cls.user = UserFactory()
cls.user_buyer = UserFactory(kind=User.KIND_BUYER, company_name="Entreprise Buyer")
cls.user_with_token = UserFactory(email="[email protected]", api_key="admin")
cls.perimeter = PerimeterFactory()
cls.sector_1 = SectorFactory()
cls.sector_2 = SectorFactory()

@patch("lemarche.api.tenders.views.get_or_create_user_from_anonymous_content")
def setup_mock_user_and_tender_creation(self, mock_get_user, user=None, title="Test Tally", extra_data=None):
"""Helper method to setup mock user and create a tender."""
user = user if user else self.user
mock_get_user.return_value = user

# Tender data
tender_data = TENDER_JSON.copy()
tender_data["title"] = title
tender_data["extra_data"] = extra_data or {}

# Tender creation
response = self.client.post(self.url, data=tender_data, content_type="application/json")
tender = Tender.objects.get(title=title)

return response, tender, user

def test_anonymous_user_cannot_create_tender(self):
url = reverse("api:tenders-list")
response = self.client.post(url, data=TENDER_JSON, content_type="application/json")
Expand All @@ -67,7 +88,7 @@ def test_user_with_valid_api_key_can_create_tender(self):
self.assertEqual(response.status_code, 201)
self.assertIn("slug", response.data.keys())
tender = Tender.objects.get(title="Test author 1")
self.assertEqual(User.objects.count(), 2 + 1) # created a new user
self.assertEqual(User.objects.count(), 3 + 1) # created a new user
self.assertEqual(tender.author.email, USER_CONTACT_EMAIL)
self.assertEqual(tender.status, tender_constants.STATUS_PUBLISHED)
self.assertEqual(tender.source, tender_constants.SOURCE_API)
Expand All @@ -81,7 +102,7 @@ def test_user_with_valid_api_key_can_create_tender(self):
self.assertEqual(response.status_code, 201)
self.assertIn("slug", response.data.keys())
tender = Tender.objects.get(title="Test author 2")
self.assertEqual(User.objects.count(), 3) # did not create a new user
self.assertEqual(User.objects.count(), 4) # did not create a new user
self.assertEqual(tender.author, self.user_with_token)
self.assertEqual(tender.status, tender_constants.STATUS_PUBLISHED)
self.assertEqual(tender.source, tender_constants.SOURCE_API)
Expand Down Expand Up @@ -135,14 +156,45 @@ def test_create_tender_with_sectors(self):
response = self.client.post(self.url, data=tender_data)
self.assertEqual(response.status_code, 400)

def test_create_tender_with_tally_source(self):
tender_data = TENDER_JSON.copy()
tender_data["title"] = "Test tally"
tender_data["extra_data"] = {"source": "TALLY"}
response = self.client.post(self.url, data=tender_data, content_type="application/json")
@patch("lemarche.api.tenders.views.add_to_contact_list")
def test_create_tender_with_tally_source(self, mock_add_to_contact_list):
extra_data = {"source": "TALLY"}
response, tender, user = self.setup_mock_user_and_tender_creation(title="Test tally", extra_data=extra_data)

mock_add_to_contact_list.assert_called_once()
args, kwargs = mock_add_to_contact_list.call_args

self.assertEqual(response.status_code, 201)
tender = Tender.objects.get(title="Test tally")
self.assertEqual(tender.source, tender_constants.SOURCE_TALLY)
# Check other arguments like user, type, and source
self.assertEqual(kwargs["user"], user)
self.assertEqual(kwargs["type"], "signup")
self.assertEqual(kwargs["source"], user_constants.SOURCE_TALLY_FORM)
# Verify that `tender` is an instance of Tender
self.assertIsInstance(
kwargs.get("tender"), Tender, "Expected an instance of Tender for the 'tender' argument."
)

@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.CreateContact")
def test_create_contact_call_has_user_buyer_attributes(self, mock_create_contact):
"""Test CreateContact call contains user buyer attributes"""
extra_data = {"source": "TALLY"}
_, tender, user = self.setup_mock_user_and_tender_creation(
title="Test tally", user=self.user_buyer, extra_data=extra_data
)
sectors = tender.sectors.all()

mock_create_contact.assert_called_once()
args, kwargs = mock_create_contact.call_args
attributes = kwargs["attributes"]

self.assertEqual(kwargs["email"], user.email)
self.assertIn(settings.BREVO_CL_SIGNUP_BUYER_ID, kwargs["list_ids"])
self.assertEqual(attributes["MONTANT_BESOIN_ACHETEUR"], tender.amount_int)
self.assertEqual(attributes["TYPE_BESOIN_ACHETEUR"], tender.kind)

if sectors.exists():
attributes["TYPE_VERTICALE_ACHETEUR"] = sectors.first().name

def test_create_tender_with_different_contact_data(self):
tender_data = TENDER_JSON.copy()
Expand Down
4 changes: 3 additions & 1 deletion lemarche/api/tenders/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from lemarche.tenders import constants as tender_constants
from lemarche.tenders.models import Tender
from lemarche.users import constants as user_constants
from lemarche.utils.emails import add_to_contact_list
from lemarche.www.tenders.utils import get_or_create_user_from_anonymous_content


Expand Down Expand Up @@ -73,13 +74,14 @@ def perform_create(self, serializer: TenderSerializer):
serializer.validated_data.pop("contact_kind", None)
serializer.validated_data.pop("contact_buyer_kind_detail", None)
# create Tender
serializer.save(
tender = serializer.save(
author=user,
status=tender_constants.STATUS_PUBLISHED,
published_at=timezone.now(),
source=tender_source,
import_raw_object=self.request.data,
)
add_to_contact_list(user=user, type="signup", source=user_source, tender=tender)


class TenderKindViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
Expand Down
47 changes: 37 additions & 10 deletions lemarche/utils/apis/api_brevo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

import sib_api_v3_sdk
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from huey.contrib.djhuey import task
from sib_api_v3_sdk.rest import ApiException

from lemarche.tenders import constants as tender_constants
from lemarche.utils.constants import EMAIL_SUBJECT_PREFIX
from lemarche.utils.data import sanitize_to_send_by_email
from lemarche.utils.urls import get_object_admin_url, get_object_share_url
Expand All @@ -28,26 +30,51 @@ def get_api_client():
return sib_api_v3_sdk.ApiClient(config)


def create_contact(user, list_id: int):
def create_contact(user, list_id: int, tender=None):
"""
Brevo docs
- Python library: https://github.com/sendinblue/APIv3-python-library/blob/master/docs/CreateContact.md
- API: https://developers.brevo.com/reference/createcontact
"""
api_client = get_api_client()
api_instance = sib_api_v3_sdk.ContactsApi(api_client)

attributes = {
"NOM": sanitize_to_send_by_email(user.last_name.capitalize()),
"PRENOM": sanitize_to_send_by_email(user.first_name.capitalize()),
"DATE_INSCRIPTION": user.created_at,
"TYPE_ORGANISATION": user.buyer_kind_detail,
"NOM_ENTREPRISE": sanitize_to_send_by_email(user.company_name.capitalize()),
"SMS": sanitize_to_send_by_email(user.phone_display),
"MONTANT_BESOIN_ACHETEUR": None,
"TYPE_BESOIN_ACHETEUR": None,
"TYPE_VERTICALE_ACHETEUR": None,
# WHATSAPP, TYPE_ORGANISATION, LIEN_FICHE_COMMERCIALE, TAUX_DE_COMPLETION
}

try:
tender = user.tenders.get(id=tender.id)
first_sector = tender.sectors.first()
attributes["MONTANT_BESOIN_ACHETEUR"] = tender.amount_int
attributes["TYPE_BESOIN_ACHETEUR"] = tender.kind

# Check if there is one sector whose tender source is TALLY
if tender.source == tender_constants.SOURCE_TALLY and first_sector:
attributes["TYPE_VERTICALE_ACHETEUR"] = first_sector.name
else:
attributes["TYPE_VERTICALE_ACHETEUR"] = None

except ObjectDoesNotExist:
print("L'objet Tender demandé n'existe pas pour cet utilisateur.")
except AttributeError as e:
print(f"Erreur d'attribut : {e}")
except Exception as e:
print(f"Une erreur inattendue est survenue : {e}")

new_contact = sib_api_v3_sdk.CreateContact(
email=user.email,
list_ids=[list_id],
attributes={
"NOM": sanitize_to_send_by_email(user.last_name.capitalize()),
"PRENOM": sanitize_to_send_by_email(user.first_name.capitalize()),
"DATE_INSCRIPTION": user.created_at,
"TYPE_ORGANISATION": user.buyer_kind_detail,
"NOM_ENTREPRISE": sanitize_to_send_by_email(user.company_name.capitalize()),
"SMS": sanitize_to_send_by_email(user.phone_display),
# WHATSAPP, TYPE_ORGANISATION, LIEN_FICHE_COMMERCIALE, TAUX_DE_COMPLETION
},
attributes=attributes,
ext_id=str(user.id),
update_enabled=True,
)
Expand Down
4 changes: 2 additions & 2 deletions lemarche/utils/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def whitelist_recipient_list(recipient_list):
return [email for email in recipient_list if (email and email.endswith("beta.gouv.fr"))]


def add_to_contact_list(user, type: str, source: str = user_constants.SOURCE_SIGNUP_FORM):
def add_to_contact_list(user, type: str, tender=None, source: str = user_constants.SOURCE_SIGNUP_FORM):
"""Add user to contactlist

Args:
Expand All @@ -65,7 +65,7 @@ def add_to_contact_list(user, type: str, source: str = user_constants.SOURCE_SIG
if type == "signup":
contact_list_id = api_mailjet.get_mailjet_cl_on_signup(user, source)
if user.kind == user.KIND_BUYER:
api_brevo.create_contact(user=user, list_id=settings.BREVO_CL_SIGNUP_BUYER_ID)
api_brevo.create_contact(user=user, list_id=settings.BREVO_CL_SIGNUP_BUYER_ID, tender=tender)
elif user.kind == user.KIND_SIAE:
api_brevo.create_contact(user=user, list_id=settings.BREVO_CL_SIGNUP_SIAE_ID)
elif type == "buyer_search":
Expand Down
2 changes: 2 additions & 0 deletions lemarche/www/pages/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from lemarche.tenders.models import Tender, TenderStepsData
from lemarche.users import constants as user_constants
from lemarche.users.models import User
from lemarche.utils.emails import add_to_contact_list
from lemarche.utils.tracker import track
from lemarche.www.pages.forms import (
CompanyReferenceCalculatorForm,
Expand Down Expand Up @@ -342,6 +343,7 @@ def csrf_failure(request, reason=""): # noqa C901
# create tender
if is_adding:
tender: Tender = create_tender_from_dict(tender_dict)
add_to_contact_list(user=user, type="signup", source=user_constants.SOURCE_TENDER_FORM, tender=tender)
elif is_update:
slug = request.path.split("/")[-1]
tender: Tender = Tender.objects.get(slug=slug)
Expand Down
50 changes: 50 additions & 0 deletions lemarche/www/tenders/tests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
from datetime import timedelta
from unittest import mock
from unittest.mock import patch

from django.conf import settings
from django.contrib.gis.geos import Point
Expand All @@ -21,6 +22,7 @@
from lemarche.tenders.enums import SurveyDoesNotExistQuestionChoices, SurveyScaleQuestionChoices
from lemarche.tenders.factories import TenderFactory, TenderQuestionFactory
from lemarche.tenders.models import Tender, TenderSiae, TenderStepsData
from lemarche.users import constants as user_constants
from lemarche.users.factories import UserFactory
from lemarche.users.models import User
from lemarche.utils import constants
Expand All @@ -30,6 +32,7 @@
class TenderCreateViewTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = UserFactory()
cls.user_siae = UserFactory(kind=User.KIND_SIAE)
cls.user_buyer = UserFactory(kind=User.KIND_BUYER, company_name="Entreprise Buyer")
cls.sectors = [SectorFactory().slug for _ in range(3)]
Expand Down Expand Up @@ -101,6 +104,18 @@ def _check_every_step(self, tenders_step_data, final_redirect_page: str = revers
tender_step_data.steps_data[-1]["tender_create_multi_step_view-current_step"],
)

@patch("lemarche.www.tenders.views.get_or_create_user")
def setup_mock_user_and_tender_creation(self, mock_get_user, user=None, title="Test Tender Form"):
"""Helper method to setup mock user"""
user = user if user else self.user
mock_get_user.return_value = user

tenders_step_data = self._generate_fake_data_form({"general-title": title})
self._check_every_step(tenders_step_data, final_redirect_page=reverse("siae:search_results"))
tender = Tender.objects.get(title=title)

return tender, user

def test_anyone_can_access_create_tender(self):
# anonymous user
url = reverse("tenders:create")
Expand Down Expand Up @@ -249,6 +264,41 @@ def test_tender_wizard_form_questions_list(self):
self.assertEqual(tender.questions.count(), len(initial_data_questions_list)) # count is 2
self.assertEqual(tender.questions_list()[0].get("text"), initial_data_questions_list[0].get("text"))

@patch("lemarche.www.tenders.views.add_to_contact_list")
def test_args_in_add_to_contact_list_call(self, mock_add_to_contact_list):
"""Test arguments in `add_to_contact_list` call"""
tender, user = self.setup_mock_user_and_tender_creation()

mock_add_to_contact_list.assert_called_once()
args, kwargs = mock_add_to_contact_list.call_args

# Check arguments like user, type, and source
self.assertEqual(kwargs["user"], user)
self.assertEqual(kwargs["type"], "signup")
self.assertEqual(kwargs["source"], user_constants.SOURCE_TENDER_FORM)
# Verify that `tender` is an instance of Tender
self.assertIsInstance(
kwargs.get("tender"), Tender, "Expected an instance of Tender for the 'tender' argument."
)

@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.CreateContact")
def test_create_contact_call_has_user_buyer_attributes(self, mock_create_contact):
"""Test CreateContact call contains user buyer attributes"""
tender, user = self.setup_mock_user_and_tender_creation(user=self.user_buyer)
tender.save()

mock_create_contact.assert_called_once()
args, kwargs = mock_create_contact.call_args
attributes = kwargs["attributes"]

self.assertEqual(kwargs["email"], user.email)
self.assertIn(settings.BREVO_CL_SIGNUP_BUYER_ID, kwargs["list_ids"])
self.assertEqual(attributes["MONTANT_BESOIN_ACHETEUR"], tender.amount_int)
self.assertEqual(attributes["TYPE_BESOIN_ACHETEUR"], tender.kind)
self.assertIsNone(
attributes["TYPE_VERTICALE_ACHETEUR"], "Expected TYPE_VERTICALE_ACHETEUR to be None for non-TALLY sources"
)


class TenderMatchingTest(TestCase):
@classmethod
Expand Down
2 changes: 0 additions & 2 deletions lemarche/www/tenders/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from lemarche.tenders.models import Tender, TenderQuestion
from lemarche.users import constants as user_constants
from lemarche.users.models import User
from lemarche.utils.emails import add_to_contact_list
from lemarche.www.auth.tasks import send_new_user_password_reset_link


Expand Down Expand Up @@ -79,7 +78,6 @@ def get_or_create_user_from_anonymous_content(
)
if created and settings.BITOUBI_ENV == "prod":
send_new_user_password_reset_link(user)
add_to_contact_list(user=user, type="signup", source=source)
return user


Expand Down
4 changes: 3 additions & 1 deletion lemarche/www/tenders/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from lemarche.users.models import User
from lemarche.utils import constants, settings_context_processors
from lemarche.utils.data import get_choice
from lemarche.utils.emails import add_to_contact_list
from lemarche.utils.mixins import (
SesameSiaeMemberRequiredMixin,
SesameTenderAuthorRequiredMixin,
Expand Down Expand Up @@ -229,7 +230,6 @@ def done(self, _, form_dict, **kwargs):
tender_dict = cleaned_data | {"author": user, "source": tender_constants.SOURCE_FORM}
is_draft: bool = self.request.POST.get("is_draft", False)
self.save_instance_tender(tender_dict=tender_dict, form_dict=form_dict, is_draft=is_draft)

# remove steps data
uuid = self.request.session.get("tender_steps_data_uuid", None)
if uuid:
Expand All @@ -254,6 +254,8 @@ def done(self, _, form_dict, **kwargs):
message=self.get_success_message(cleaned_data, self.instance, is_draft=is_draft),
extra_tags="modal_message_bizdev",
)
tender = self.instance
add_to_contact_list(user=user, type="signup", source=user_constants.SOURCE_TENDER_FORM, tender=tender)
return redirect(self.get_success_url())

def get_success_url(self):
Expand Down
Loading