diff --git a/lemarche/crm/management/commands/crm_brevo_sync_companies.py b/lemarche/crm/management/commands/crm_brevo_sync_companies.py index 8801261cf..41b08f5cc 100644 --- a/lemarche/crm/management/commands/crm_brevo_sync_companies.py +++ b/lemarche/crm/management/commands/crm_brevo_sync_companies.py @@ -37,18 +37,23 @@ def handle(self, recently_updated: bool, **options): siaes_qs = siaes_qs.filter(updated_at__gte=two_weeks_ago) self.stdout.write(f"Sync Siae > Brevo: {siaes_qs.count()} recently updated") - # Step 2: loop on the siaes + # Step 2: Add the 90-day limited annotations + siaes_qs = siaes_qs.with_tender_stats() + + # Step 3: loop on the siaes for index, siae in enumerate(siaes_qs): + print(f"HANDLE : Siae id : {siae.id}, old siae.extra_data = {siae.extra_data}") new_extra_data = { "completion_rate": siae.completion_rate, - "tender_email_send_count": siae.tender_email_send_count, - "recent_tender_detail_click_count": siae.recent_tender_detail_click_count, + "recent_tender_email_send_count": getattr(siae, "tender_email_send_count_annotated", 0), + "recent_tender_detail_click_count": getattr(siae, "tender_detail_contact_click_count_annotated", 0), } # extra_data update if needed if siae.extra_data != new_extra_data: siae.extra_data = new_extra_data siae.save(update_fields=["extra_data"]) + print(f"HANDLE : Siae id : {siae.id}, new siae.extra_data = {siae.extra_data}") api_brevo.create_or_update_company(siae) if (index % 10) == 0: # avoid API rate-limiting time.sleep(1) diff --git a/lemarche/crm/tests.py b/lemarche/crm/tests.py index b5e3f7f4a..515099569 100644 --- a/lemarche/crm/tests.py +++ b/lemarche/crm/tests.py @@ -1,19 +1,43 @@ +from datetime import timedelta from unittest.mock import MagicMock, patch from django.core.management import call_command from django.test import TestCase +from django.utils import timezone from lemarche.siaes.factories import SiaeFactory +from lemarche.siaes.models import Siae +from lemarche.tenders.factories import TenderFactory +from lemarche.tenders.models import TenderSiae +from lemarche.users.factories import UserFactory +from lemarche.users.models import User from lemarche.utils.apis.api_brevo import create_or_update_company from lemarche.utils.urls import get_object_admin_url, get_object_share_url +now = timezone.now() +date_tomorrow = now + timedelta(days=1) + + class CrmBrevoSyncCompaniesCommandTests(TestCase): @classmethod def setUpTestData(cls): """Siae instances initialization""" - cls.siae1 = SiaeFactory(name="Test Company 1") - cls.siae2 = SiaeFactory() + cls.recent_date = timezone.now() - timedelta(days=30) + cls.old_date = timezone.now() - timedelta(days=100) + + cls.siae_1 = SiaeFactory(name="Test Company 1") + cls.siae_2 = SiaeFactory() + + cls.user_siae = UserFactory(kind=User.KIND_SIAE) + cls.siae_with_tender_1 = SiaeFactory(users=[cls.user_siae]) + cls.tender_with_siae_1 = TenderFactory(siaes=[cls.siae_with_tender_1], deadline_date=date_tomorrow) + TenderSiae.objects.create( + tender=cls.tender_with_siae_1, + siae=cls.siae_with_tender_1, + email_send_date=timezone.now(), + detail_contact_click_date=timezone.now(), + ) @patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.Body") def test_create_or_update_company_with_mocked_body(self, mock_body): @@ -21,25 +45,25 @@ def test_create_or_update_company_with_mocked_body(self, mock_body): mock_body_instance = MagicMock() mock_body.return_value = mock_body_instance - create_or_update_company(self.siae1) + create_or_update_company(self.siae_1) expected_attributes = { - "domain": self.siae1.website, - "phone_number": self.siae1.contact_phone_display, - "app_id": self.siae1.id, + "domain": self.siae_1.website, + "phone_number": self.siae_1.contact_phone_display, + "app_id": self.siae_1.id, "siae": True, - "active": self.siae1.is_active, - "description": self.siae1.description, - "kind": self.siae1.kind, - "address_street": self.siae1.address, - "address_post_code": self.siae1.post_code, - "address_city": self.siae1.city, - "contact_email": self.siae1.contact_email, - "logo_url": self.siae1.logo_url, - "geo_range": self.siae1.geo_range, - "app_url": get_object_share_url(self.siae1), - "app_admin_url": get_object_admin_url(self.siae1), - **self.siae1.extra_data, # includes completion_rate, tender_email_send_count, etc. + "active": self.siae_1.is_active, + "description": self.siae_1.description, + "kind": self.siae_1.kind, + "address_street": self.siae_1.address, + "address_post_code": self.siae_1.post_code, + "address_city": self.siae_1.city, + "contact_email": self.siae_1.contact_email, + "logo_url": self.siae_1.logo_url, + "geo_range": self.siae_1.geo_range, + "app_url": get_object_share_url(self.siae_1), + "app_admin_url": get_object_admin_url(self.siae_1), + **self.siae_1.extra_data, # includes completion_rate, tender_email_send_count, etc. } mock_body.assert_called_once_with(name="Test Company 1", attributes=expected_attributes) @@ -49,44 +73,56 @@ def test_new_siaes_are_synced_in_brevo(self, mock_create_or_update_company): """Test new siaes are synced in brevo""" call_command("crm_brevo_sync_companies") - mock_create_or_update_company.assert_any_call(self.siae1) - mock_create_or_update_company.assert_any_call(self.siae2) + mock_create_or_update_company.assert_any_call(self.siae_1) + mock_create_or_update_company.assert_any_call(self.siae_2) self.assertEqual(mock_create_or_update_company.call_count, 2) @patch("lemarche.utils.apis.api_brevo.create_or_update_company") def test_siae_extra_data_is_updated(self, mock_create_or_update_company): """Test siae is updated if extra_data is changed.""" - initial_extra_data = self.siae2.extra_data.copy() - - self.siae2.completion_rate = 70 - self.siae2.tender_email_send_count = 10 - self.siae2.save() + initial_extra_data = self.siae_with_tender_1.extra_data.copy() + print("TEST : initial_extra_data = ", initial_extra_data) + print("email_send_date : ", self.siae_with_tender_1.tendersiae_set.first().email_send_date) + print("detail_contact_click_date : ", self.siae_with_tender_1.tendersiae_set.first().detail_contact_click_date) + # Simulate updates + siae_with_tender_stats = Siae.objects.with_tender_stats().filter(id=self.siae_with_tender_1.id).first() + # siae_with_tender_stats.update( + # email_send_date=now - timedelta(days=45), + # detail_contact_click_date=now - timedelta(days=30), + # ) + print("email_send_date : ", siae_with_tender_stats.email_send_date) + print("detail_contact_click_date : ", siae_with_tender_stats.detail_contact_click_date) call_command("crm_brevo_sync_companies", recently_updated=True) self.assertTrue(mock_create_or_update_company.called) + self.siae_with_tender_1.refresh_from_db() - self.siae2.refresh_from_db() expected_extra_data = { - "completion_rate": self.siae2.completion_rate, - "tender_email_send_count": self.siae2.tender_email_send_count, - "recent_tender_detail_click_count": self.siae2.recent_tender_detail_click_count, + "completion_rate": self.siae_with_tender_1.completion_rate, + "recent_tender_email_send_count": siae_with_tender_stats.tender_email_send_count_annotated, + "recent_tender_detail_click_count": siae_with_tender_stats.tender_detail_contact_click_count_annotated, } - self.assertNotEqual(initial_extra_data, expected_extra_data, "siae.extra_data aurait dût être mis à jour.") + print("TEST : self.siae_2.extra_data = ", self.siae_with_tender_1.extra_data) + print(("TEST : expected_extra_data = ", expected_extra_data)) + self.assertNotEqual(initial_extra_data, expected_extra_data, "siae.extra_data aurait dû être mis à jour.") + self.assertEqual( + self.siae_with_tender_1.extra_data, expected_extra_data, "siae.extra_data n'est pas conforme aux attentes." + ) @patch("lemarche.utils.apis.api_brevo.create_or_update_company") def test_siae_extra_data_is_not_updated_if_no_change(self, mock_create_or_update_company): """Test siae is not updated if no change in extra_data.""" - initial_extra_data = self.siae2.extra_data.copy() + initial_extra_data = self.siae_2.extra_data.copy() call_command("crm_brevo_sync_companies", recently_updated=True) self.assertTrue(mock_create_or_update_company.called) - self.siae2.refresh_from_db() + self.siae_2.refresh_from_db() self.assertEqual( initial_extra_data, - self.siae2.extra_data, + self.siae_2.extra_data, "siae.extra_data a été mis à jour alors qu'il n'y avait pas de changement.", ) diff --git a/lemarche/siaes/models.py b/lemarche/siaes/models.py index b9ca0befb..08b33db6d 100644 --- a/lemarche/siaes/models.py +++ b/lemarche/siaes/models.py @@ -1,4 +1,4 @@ -from datetime import timedelta +# from datetime import datetime, timedelta from uuid import uuid4 from django.conf import settings @@ -497,9 +497,10 @@ def filter_with_tender_tendersiae_status(self, tender, tendersiae_status=None): return qs.distinct() - def with_tender_stats(self): + def with_tender_stats(self, days=0): """ - Enrich each Siae with stats on their linked Tender + Enrich each Siae with stats on their linked Tender. + Optionally, limit the stats to the last `days` days. """ return self.annotate( tender_count_annotated=Count("tenders", distinct=True), @@ -968,14 +969,6 @@ def __init__(self, *args, **kwargs): for field_name in self.TRACK_UPDATE_FIELDS: setattr(self, f"__previous_{field_name}", getattr(self, field_name)) - # siae.extra_data tracked for updates in Brevo sync command - if not self.extra_data: - self.extra_data = { - "completion_rate": self.completion_rate, - "tender_email_send_count": self.tender_email_send_count, - "recent_tender_detail_click_count": self.recent_tender_detail_click_count, - } - def set_slug(self, with_uuid=False): """ The slug field should be unique. @@ -1303,13 +1296,6 @@ def elasticsearch_index_metadata(self): return metadata - @property - def recent_tender_detail_click_count(self): - """Calcule le nombre de clics récents pour cette instance de Siae.""" - if not self.pk: - return 0 - return self.tendersiae_set.filter(detail_contact_click_date__gte=timezone.now() - timedelta(days=90)).count() - def sectors_list_string(self, display_max=3): sectors_name_list = self.sectors.form_filter_queryset().values_list("name", flat=True) if display_max and len(sectors_name_list) > display_max: