Skip to content

Commit

Permalink
feat(brevo): create all tenders in Brevo on validation
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasAbrn committed Feb 10, 2025
1 parent 32cb71b commit 3959c2c
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 152 deletions.
5 changes: 0 additions & 5 deletions .cursorrules

This file was deleted.

9 changes: 0 additions & 9 deletions lemarche/tenders/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from lemarche.users import constants as user_constants
from lemarche.users.models import User
from lemarche.utils.admin.admin_site import admin_site
from lemarche.utils.apis import api_brevo
from lemarche.utils.fields import ChoiceArrayField, pretty_print_readonly_jsonfield
from lemarche.www.tenders.tasks import restart_send_tender_task

Expand Down Expand Up @@ -786,14 +785,6 @@ def response_change(self, request, obj: Tender):
return HttpResponseRedirect("./#structures") # redirect to structures sections
if request.POST.get("_validate_send_to_siaes"):
obj.set_validated()
if obj.is_followed_by_us:
try:
api_brevo.create_deal(tender=obj, owner_email=request.user.email)
# we link deal(tender) with author contact
api_brevo.link_deal_with_contact_list(tender=obj)
self.message_user(request, "Ce dépôt de besoin a été synchronisé avec Brevo")
except Exception as e:
self.message_user(request, f"Erreur dans la synchronisation du DDB avec Brevo {str(e)}")
self.message_user(request, "Ce dépôt de besoin a été validé. Il sera envoyé en temps voulu :)")
return HttpResponseRedirect(".")
if request.POST.get("_validate_send_to_commercial_partners"):
Expand Down
21 changes: 21 additions & 0 deletions lemarche/tenders/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
from datetime import datetime, timedelta
from uuid import uuid4

Expand Down Expand Up @@ -27,6 +28,8 @@
from django_extensions.db.fields import ShortUUIDField
from phonenumber_field.modelfields import PhoneNumberField
from shortuuid import uuid
from django.db.models.signals import post_save
from django.dispatch import receiver

from lemarche.perimeters.models import Perimeter
from lemarche.siaes import constants as siae_constants
Expand All @@ -47,6 +50,9 @@
from lemarche.utils.validators import OptionalSchemeURLValidator


logger = logging.getLogger(__name__)


def get_perimeter_filter(siae):
return (
Q(perimeters__post_codes__contains=[siae.post_code])
Expand Down Expand Up @@ -1273,3 +1279,18 @@ class Meta:

def __str__(self):
return f"{self.uuid} - {self.created_at}"


@receiver(post_save, sender=Tender)
def tender_post_save(sender, instance, created, **kwargs):
"""
Create a deal in Brevo when a tender is created
"""
if created:
from lemarche.utils.apis.api_brevo import create_deal

try:
create_deal(tender=instance, owner_email=instance.author.email)
except Exception as e:
# Log the error but don't prevent tender creation
logger.error(f"Error creating Brevo deal for tender {instance.id}: {e}")
89 changes: 0 additions & 89 deletions lemarche/tenders/tests/test_admin.py

This file was deleted.

77 changes: 77 additions & 0 deletions lemarche/tenders/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from lemarche.users.models import User
from lemarche.utils.admin.admin_site import MarcheAdminSite, get_admin_change_view_url
from lemarche.www.tenders import utils as tender_utils
from unittest.mock import patch


date_today = timezone.now()
Expand Down Expand Up @@ -1140,3 +1141,79 @@ def test_no_matching_ranges(self):
"""Test when no ranges match the criteria."""
expected_keys = [tender_constants.AMOUNT_RANGE_0_1]
self.assertListEqual(find_amount_ranges(100, "lte"), expected_keys)


@patch("lemarche.utils.apis.api_brevo.create_deal")
def test_set_validated_creates_brevo_deal(self, mock_create_deal):
"""Test that set_validated creates a Brevo deal"""
tender = TenderFactory(status=tender_constants.STATUS_DRAFT)

tender.set_validated()

# Verify Brevo API call
mock_create_deal.assert_called_once_with(tender=tender, owner_email=tender.author.email)

# Verify tender status changes
self.assertEqual(tender.status, tender_constants.STATUS_VALIDATED)
self.assertIsNotNone(tender.validated_at)

# Verify log entry
self.assertEqual(len(tender.logs), 1)
self.assertEqual(tender.logs[0]["action"], "validate")


@patch("lemarche.utils.apis.api_brevo.create_deal")
def test_set_validated_handles_brevo_error(self, mock_create_deal):
"""Test that set_validated handles Brevo API errors gracefully"""
tender = TenderFactory(status=tender_constants.STATUS_DRAFT)

# Simulate API error
mock_create_deal.side_effect = Exception("API Error")

tender.set_validated()

# Verify API call
mock_create_deal.assert_called_once_with(tender=tender, owner_email=tender.author.email)

# Verify tender was still validated despite API error
self.assertEqual(tender.status, tender_constants.STATUS_VALIDATED)
self.assertIsNotNone(tender.validated_at)

# Verify log entry
self.assertEqual(len(tender.logs), 1)
self.assertEqual(tender.logs[0]["action"], "validate")


@patch("lemarche.utils.apis.api_brevo.create_deal")
def test_set_validated_only_works_on_draft(self, mock_create_deal):
"""Test that set_validated only works on draft tenders"""
tender = TenderFactory(status=tender_constants.STATUS_VALIDATED)

tender.set_validated()

# Verify no API call was made
mock_create_deal.assert_not_called()

# Verify no changes were made
self.assertEqual(tender.status, tender_constants.STATUS_VALIDATED)


@patch("lemarche.utils.apis.api_brevo.create_deal")
def test_create_deal_on_tender_creation(self, mock_create_deal):
"""Test that creating a new tender creates a Brevo deal"""
tender = TenderFactory()

# Verify create_deal was called once with the tender instance
mock_create_deal.assert_called_once_with(tender=tender, owner_email=tender.author.email)

# Create another tender
tender2 = TenderFactory()
self.assertEqual(mock_create_deal.call_count, 2)
mock_create_deal.assert_called_with(tender=tender2, owner_email=tender2.author.email)

# Update existing tender
tender.title = "Updated Title"
tender.save()

# Call count should still be 2 since we only sync on creation
self.assertEqual(mock_create_deal.call_count, 2)
48 changes: 2 additions & 46 deletions lemarche/utils/apis/api_brevo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
import time
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, Optional, Union

import sib_api_v3_sdk
from django.conf import settings
Expand Down Expand Up @@ -155,7 +155,7 @@ def create_deal(tender: "Tender", owner_email: str) -> None:
- https://developers.brevo.com/reference/post_crm-deals
Args:
tender (Tender): Object with tender details like title, description, amount, and deadlines.
tender (Tender): Object with tender details like title, description, and deadlines.
owner_email (str): The email address of the deal's owner.
Raises:
Expand All @@ -166,14 +166,9 @@ def create_deal(tender: "Tender", owner_email: str) -> None:
body_deal = sib_api_v3_sdk.Body3(
name=tender.title,
attributes={
# default attributes
# pipeline, deal_stage, closed_won_reason, closed_lost_reason, total_revenue, lost_reason
"deal_description": tender.description,
"deal_owner": owner_email,
"close_date": tender.deadline_date.strftime("%Y-%m-%d"),
# custom attributes
"amount": tender.amount_int,
"tender_admin_url": tender.get_admin_url(),
},
)

Expand All @@ -189,45 +184,6 @@ def create_deal(tender: "Tender", owner_email: str) -> None:
raise ApiException(e)


def link_deal_with_contact_list(tender: "Tender", contact_list: Optional[List[int]] = None) -> None:
"""
Links a Brevo deal to a list of contacts. If no contact list is provided, it defaults
to linking the deal with the tender's author.
This function uses the tender's stored deal ID and either a provided list of contact IDs or the
tender author's contact ID to link contacts to the deal in the Brevo CRM.
Args:
tender (Tender): The tender object containing the Brevo deal ID and author's contact ID.
contact_list (list of int, optional): List of contact IDs to be linked with the deal. Defaults to None.
Raises:
ApiException: If an error occurs during the linking process in the Brevo API.
"""
api_client = get_api_client()
api_instance = sib_api_v3_sdk.DealsApi(api_client)

if settings.BITOUBI_ENV not in ENV_NOT_ALLOWED:
try:
# get brevo ids
brevo_crm_deal_id = tender.brevo_deal_id
# Default to the author's contact ID if no contact list is provided
if not contact_list:
contact_list = [tender.author.brevo_contact_id]

# cleanup
contact_list = [id for id in contact_list if id is not None]

# link deal with contact_list
if len(contact_list):
# https://github.com/sendinblue/APIv3-python-library/blob/master/docs/Body5.md
body_link_deal_contact = sib_api_v3_sdk.Body5(link_contact_ids=contact_list)
api_instance.crm_deals_link_unlink_id_patch(brevo_crm_deal_id, body_link_deal_contact)

except ApiException as e:
logger.error("Exception when calling Brevo->DealApi->crm_deals_link_unlink_id_patch: %s\n" % e)


def link_company_with_contact_list(siae: "Siae", contact_list: list = None):
"""
Links a Brevo company to a list of contacts. If no contact list is provided, it defaults
Expand Down
6 changes: 3 additions & 3 deletions lemarche/www/tenders/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,9 @@ class Meta:

def __init__(self, tender_survey_transactioned_answer=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["survey_transactioned_answer"].label = (
"Avez-vous contractualisé avec un prestataire trouvé via le Marché de l'inclusion ?"
)
self.fields[
"survey_transactioned_answer"
].label = "Avez-vous contractualisé avec un prestataire trouvé via le Marché de l'inclusion ?"
self.fields["survey_transactioned_amount"].label = "Quel est le montant de la transaction ? (facultatif)"
self.fields["survey_transactioned_feedback"].label = "Partagez-nous votre retour d'expérience (facultatif)"
self.fields["survey_transactioned_feedback"].widget.attrs.update(
Expand Down

0 comments on commit 3959c2c

Please sign in to comment.