diff --git a/lemarche/siaes/models.py b/lemarche/siaes/models.py index 348447242..1857dd7dc 100644 --- a/lemarche/siaes/models.py +++ b/lemarche/siaes/models.py @@ -353,77 +353,6 @@ def with_in_user_favorite_list_stats(self, user): def has_contact_email(self): return self.exclude(contact_email__isnull=True).exclude(contact_email__exact="") - def filter_with_tender(self, tender, tendersiae_status=None): # noqa C901 - """ - Filter Siaes with tenders: - - first we filter the Siae that are live + can be contacted - - then we filter on the sectors - - then we filter on the perimeters: - - if tender is made for country area, we filter with siae_geo_range=country - - else we filter on the perimeters - - then we filter on presta_type - - then we filter on kind - - finally we filter with the tendersiae_status passed as a parameter - - Args: - tender (Tender): Tender used to make the matching - """ - qs = self.tender_matching_query_set() - # filter by sectors - if tender.sectors.count(): - qs = qs.filter_sectors(tender.sectors.all()) - # filter by perimeters - if tender.is_country_area: # for all country - qs = qs.with_country_geo_range() - else: - # filter by tender.distance_location km around the given city in location - if ( - tender.location - and tender.location.kind == Perimeter.KIND_CITY - and tender.distance_location - and tender.distance_location > 0 - ): - qs = qs.within(tender.location.coords, tender.distance_location, tender.include_country_area) - elif tender.perimeters.count() and tender.include_country_area: # perimeters and all country - qs = qs.geo_range_in_perimeter_list( - tender.perimeters.all(), with_country=False, include_country_area=True - ) - elif tender.perimeters.count(): # only perimeters - qs = qs.geo_range_in_perimeter_list( - tender.perimeters.all(), with_country=True - ).exclude_country_geo_range() - elif tender.include_country_area: - qs = qs.with_country_geo_range() - # filter by presta_type - if len(tender.presta_type): - qs = qs.filter(presta_type__overlap=tender.presta_type) - # filter by siae_kind - if len(tender.siae_kind): - qs = qs.filter(kind__in=tender.siae_kind) - - # tender status - if tendersiae_status == "INTERESTED": - qs = qs.filter(tendersiae__tender=tender, tendersiae__detail_contact_click_date__isnull=False) - qs = qs.order_by("-tendersiae__detail_contact_click_date") - elif tendersiae_status == "VIEWED": - qs = qs.filter( - Q(tendersiae__tender=tender) - & ( - Q(tendersiae__email_link_click_date__isnull=False) - | Q(tendersiae__detail_display_date__isnull=False) - ) - ) - qs = qs.order_by("-tendersiae__email_link_click_date") - elif tendersiae_status == "COCONTRACTED": - qs = qs.filter(tendersiae__tender=tender, tendersiae__detail_cocontracting_click_date__isnull=False) - qs = qs.order_by("-tendersiae__detail_cocontracting_click_date") - elif tendersiae_status == "ALL": - # why need to filter more ? - qs = qs.filter(tendersiae__tender=tender, tendersiae__email_send_date__isnull=False) - qs = qs.order_by("-tendersiae__email_send_date") - - return qs.distinct() - def filter_with_tender_through_activities(self, tender, tendersiae_status=None): """ Filter Siaes with tenders: diff --git a/lemarche/tenders/models.py b/lemarche/tenders/models.py index f15f2ddc8..582364653 100644 --- a/lemarche/tenders/models.py +++ b/lemarche/tenders/models.py @@ -745,7 +745,7 @@ def set_siae_found_list(self): """ Where the Tender-Siae matching magic happens! """ - siae_found_list = Siae.objects.filter_with_tender(self) + siae_found_list = Siae.objects.filter_with_tender_through_activities(self) self.siaes.set(siae_found_list, clear=False) if self.with_ai_matching and self.validated_at is None: diff --git a/lemarche/tenders/tests/test_commands.py b/lemarche/tenders/tests/test_commands.py index 86a4b2b46..9d6d501fb 100644 --- a/lemarche/tenders/tests/test_commands.py +++ b/lemarche/tenders/tests/test_commands.py @@ -7,7 +7,7 @@ from lemarche.sectors.factories import SectorFactory from lemarche.siaes import constants as siae_constants -from lemarche.siaes.factories import SiaeFactory +from lemarche.siaes.factories import SiaeActivityFactory, SiaeFactory from lemarche.tenders.factories import TenderFactory from lemarche.tenders.models import TenderSiae from lemarche.users.factories import UserFactory @@ -22,26 +22,35 @@ def setUpTestData(cls): cls.siae1 = SiaeFactory( is_active=True, kind=siae_constants.KIND_AI, + ) + siae1_activity = SiaeActivityFactory( + siae=cls.siae1, presta_type=[siae_constants.PRESTA_PREST, siae_constants.PRESTA_BUILD], - geo_range=siae_constants.GEO_RANGE_COUNTRY, + with_country_perimeter=True, ) - cls.siae1.sectors.add(cls.sector) + siae1_activity.sectors.add(cls.sector) cls.siae2 = SiaeFactory( is_active=True, kind=siae_constants.KIND_AI, + ) + siae2_activity = SiaeActivityFactory( + siae=cls.siae2, presta_type=[siae_constants.PRESTA_PREST, siae_constants.PRESTA_BUILD], - geo_range=siae_constants.GEO_RANGE_COUNTRY, + with_country_perimeter=True, ) - cls.siae2.sectors.add(cls.sector) + siae2_activity.sectors.add(cls.sector) cls.siae3 = SiaeFactory( is_active=True, kind=siae_constants.KIND_AI, + ) + siae3_activity = SiaeActivityFactory( + siae=cls.siae3, presta_type=[siae_constants.PRESTA_PREST, siae_constants.PRESTA_BUILD], - geo_range=siae_constants.GEO_RANGE_COUNTRY, + with_country_perimeter=True, ) - cls.siae3.sectors.add(cls.sector) + siae3_activity.sectors.add(cls.sector) cls.author = UserFactory(kind=User.KIND_BUYER) cls.tender_before = TenderFactory( diff --git a/lemarche/tenders/tests/test_models.py b/lemarche/tenders/tests/test_models.py index 97ee5d791..0ff4d5f93 100644 --- a/lemarche/tenders/tests/test_models.py +++ b/lemarche/tenders/tests/test_models.py @@ -15,7 +15,7 @@ from lemarche.perimeters.models import Perimeter from lemarche.sectors.factories import SectorFactory, SectorGroupFactory from lemarche.siaes import constants as siae_constants -from lemarche.siaes.factories import SiaeFactory +from lemarche.siaes.factories import SiaeActivityFactory, SiaeFactory from lemarche.siaes.models import Siae from lemarche.tenders import constants as tender_constants from lemarche.tenders.admin import TenderAdmin @@ -194,19 +194,25 @@ def setUpTestData(cls): cls.siae_one = SiaeFactory( is_active=True, kind=siae_constants.KIND_AI, + ) + siae_one_activity = SiaeActivityFactory( + siae=cls.siae_one, presta_type=[siae_constants.PRESTA_PREST, siae_constants.PRESTA_BUILD], - geo_range=siae_constants.GEO_RANGE_COUNTRY, + with_country_perimeter=True, ) - cls.siae_one.sectors.add(cls.sector) + siae_one_activity.sectors.add(cls.sector) # siae found by presta_type and semantic search cls.siae_two = SiaeFactory( is_active=True, kind=siae_constants.KIND_ESAT, + ) + siae_two_activity = SiaeActivityFactory( + siae=cls.siae_two, presta_type=[siae_constants.PRESTA_BUILD], - geo_range=siae_constants.GEO_RANGE_COUNTRY, + with_country_perimeter=True, ) - cls.siae_two.sectors.add(cls.sector) + siae_two_activity.sectors.add(cls.sector) # siaes found by mocked semantic search cls.siae_three = SiaeFactory() @@ -226,7 +232,7 @@ def test_set_siae_found_list_without_semantic_search(self): validated_at=None, ) - siaes_found = Siae.objects.filter_with_tender(tender) + siaes_found = Siae.objects.filter_with_tender_through_activities(tender) tender.set_siae_found_list() tender.refresh_from_db() self.assertEqual(list(siaes_found), list(tender.siaes.all())) @@ -246,7 +252,7 @@ def test_set_siae_found_list_with_semantic_search(self): validated_at=None, ) - siaes_found = Siae.objects.filter_with_tender(tender) + siaes_found = Siae.objects.filter_with_tender_through_activities(tender) tender.set_siae_found_list() tender.refresh_from_db() @@ -925,22 +931,30 @@ def setUp(cls): siae_one = SiaeFactory( is_active=True, kind=siae_constants.KIND_AI, + coords=coords_paris, + ) + siae_one_activity = SiaeActivityFactory( + siae=siae_one, presta_type=[siae_constants.PRESTA_PREST, siae_constants.PRESTA_BUILD], geo_range=siae_constants.GEO_RANGE_CUSTOM, - coords=coords_paris, geo_range_custom_distance=100, ) + siae_two = SiaeFactory( is_active=True, kind=siae_constants.KIND_ESAT, + coords=coords_paris, + ) + siae_two_activity = SiaeActivityFactory( + siae=siae_two, presta_type=[siae_constants.PRESTA_BUILD], geo_range=siae_constants.GEO_RANGE_CUSTOM, - coords=coords_paris, geo_range_custom_distance=10, ) + for i in range(5): - siae_one.sectors.add(cls.sectors[i]) - siae_two.sectors.add(cls.sectors[i + 5]) + siae_one_activity.sectors.add(cls.sectors[i]) + siae_two_activity.sectors.add(cls.sectors[i + 5]) cls.tender = TenderFactory( sectors=cls.sectors[6:8], diff --git a/lemarche/www/siaes/forms.py b/lemarche/www/siaes/forms.py index 4148caf48..a48c22920 100644 --- a/lemarche/www/siaes/forms.py +++ b/lemarche/www/siaes/forms.py @@ -346,7 +346,7 @@ def filter_queryset(self, qs=None): # noqa C901 tender = self.cleaned_data.get("tender", None) if tender: tendersiae_status = self.cleaned_data.get("tendersiae_status", "ALL") - qs = qs.filter_with_tender(tender=tender, tendersiae_status=tendersiae_status) + qs = qs.filter_with_tender_through_activities(tender=tender, tendersiae_status=tendersiae_status) locations = self.cleaned_data.get("locations", None) if locations: diff --git a/lemarche/www/tenders/tests.py b/lemarche/www/tenders/tests.py index cd9c5b52d..4dc73a6cc 100644 --- a/lemarche/www/tenders/tests.py +++ b/lemarche/www/tenders/tests.py @@ -4,7 +4,6 @@ from unittest.mock import patch from django.conf import settings -from django.contrib.gis.geos import Point from django.contrib.messages import get_messages from django.test import TestCase from django.urls import reverse @@ -17,7 +16,6 @@ from lemarche.sectors.factories import SectorFactory from lemarche.siaes import constants as siae_constants from lemarche.siaes.factories import SiaeFactory -from lemarche.siaes.models import Siae from lemarche.tenders import constants as tender_constants from lemarche.tenders.enums import SurveyDoesNotExistQuestionChoices, SurveyScaleQuestionChoices from lemarche.tenders.factories import TenderFactory, TenderQuestionFactory @@ -300,243 +298,6 @@ def test_create_contact_call_has_user_buyer_attributes(self, mock_create_contact ) -class TenderMatchingTest(TestCase): - @classmethod - def setUpTestData(cls): - cls.sectors = [SectorFactory() for i in range(10)] - cls.perimeter_paris = PerimeterFactory(department_code="75", post_codes=["75019", "75018"]) - cls.perimeter_marseille = PerimeterFactory( - coords=Point(43.35101634452076, 5.379616625955892), insee_code="13055" - ) - cls.perimeters = [cls.perimeter_paris, PerimeterFactory(insee_code="18001")] - # by default is Paris - coords_paris = Point(48.86385199985207, 2.337071483848432) - - cls.siae_one = SiaeFactory( - is_active=True, - kind=siae_constants.KIND_AI, - presta_type=[siae_constants.PRESTA_PREST, siae_constants.PRESTA_BUILD], - geo_range=siae_constants.GEO_RANGE_CUSTOM, - coords=coords_paris, - geo_range_custom_distance=100, - ) - cls.siae_two = SiaeFactory( - is_active=True, - kind=siae_constants.KIND_ESAT, - presta_type=[siae_constants.PRESTA_BUILD], - geo_range=siae_constants.GEO_RANGE_CUSTOM, - coords=coords_paris, - geo_range_custom_distance=10, - ) - for i in range(5): - cls.siae_one.sectors.add(cls.sectors[i]) - cls.siae_two.sectors.add(cls.sectors[i + 5]) - - def test_matching_siae_presta_type(self): - tender = TenderFactory(presta_type=[], sectors=self.sectors, perimeters=self.perimeters) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 2) - tender = TenderFactory( - presta_type=[siae_constants.PRESTA_BUILD], sectors=self.sectors, perimeters=self.perimeters - ) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 2) - tender = TenderFactory( - presta_type=[siae_constants.PRESTA_PREST], sectors=self.sectors, perimeters=self.perimeters - ) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 1) - - def test_matching_siae_kind(self): - tender = TenderFactory(siae_kind=[], sectors=self.sectors, perimeters=self.perimeters) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 2) - tender = TenderFactory(siae_kind=[siae_constants.KIND_AI], sectors=self.sectors, perimeters=self.perimeters) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 1) - tender = TenderFactory( - siae_kind=[siae_constants.KIND_ESAT, siae_constants.KIND_AI], - sectors=self.sectors, - perimeters=self.perimeters, - ) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 2) - tender = TenderFactory(siae_kind=[siae_constants.KIND_SEP], sectors=self.sectors, perimeters=self.perimeters) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 0) - - def test_matching_siae_sectors(self): - tender = TenderFactory(sectors=self.sectors) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 2) - - def test_matching_siae_distance_location(self): - # create SIAE in Tours - siae_tours = SiaeFactory( - is_active=True, - kind=siae_constants.KIND_AI, - presta_type=[siae_constants.PRESTA_PREST, siae_constants.PRESTA_BUILD], - coords=Point(47.392287, 0.690049), # Tours city - ) - siae_tours.sectors.add(self.sectors[0]) - - # create SIAE in Marseille - siae_marseille = SiaeFactory( - is_active=True, - kind=siae_constants.KIND_AI, - presta_type=[siae_constants.PRESTA_PREST, siae_constants.PRESTA_BUILD], - coords=self.perimeter_marseille.coords, - geo_range=siae_constants.GEO_RANGE_COUNTRY, - ) - siae_marseille.sectors.add(self.sectors[0]) - - # create tender in Azay-le-rideau (near Tours ~25km) - perimeter_azaylerideau = PerimeterFactory(coords=Point(47.262352, 0.466372), insee_code="37017") - tender = TenderFactory( - location=perimeter_azaylerideau, - distance_location=30, - siae_kind=[siae_constants.KIND_ESAT, siae_constants.KIND_AI], - sectors=self.sectors, - ) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 1) - self.assertIn(siae_tours, siae_found_list) - - # Azay-le-rideau is less than 240km from Paris but more 550km from Marseille - tender = TenderFactory( - location=perimeter_azaylerideau, - distance_location=300, - siae_kind=[siae_constants.KIND_ESAT, siae_constants.KIND_AI], - sectors=self.sectors, - perimeters=[self.perimeter_paris], # test this option without effect when the distance is setted - ) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 3) - self.assertIn(siae_tours, siae_found_list) - self.assertIn(self.siae_one, siae_found_list) - self.assertIn(self.siae_two, siae_found_list) - - # unset distance location, perimeters is used instead, Paris as it happens - tender.distance_location = None - tender.save() - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 2) - self.assertIn(self.siae_one, siae_found_list) - self.assertIn(self.siae_two, siae_found_list) - - # set distance location and include country - tender = TenderFactory( - location=perimeter_azaylerideau, - distance_location=50, - siae_kind=[siae_constants.KIND_ESAT, siae_constants.KIND_AI], - sectors=self.sectors, - include_country_area=True, - ) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 2) - self.assertIn(siae_tours, siae_found_list) - self.assertIn(siae_marseille, siae_found_list) - - # set a department in location disable distance_location, perimeters is used instead - tender = TenderFactory( - location=PerimeterFactory( - name="Indre-et-loire", kind=Perimeter.KIND_DEPARTMENT, insee_code="37", region_code="24" - ), - distance_location=50, - siae_kind=[siae_constants.KIND_ESAT, siae_constants.KIND_AI], - sectors=self.sectors, - include_country_area=True, # check this option without effect when the distance is setted - perimeters=[self.perimeter_paris], # without effect too - ) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 3) - self.assertIn(self.siae_one, siae_found_list) - self.assertIn(self.siae_two, siae_found_list) - self.assertIn(siae_marseille, siae_found_list) - - def test_matching_siae_perimeters_custom(self): - # add Siae with geo_range_country - siae_country = SiaeFactory(is_active=True, geo_range=siae_constants.GEO_RANGE_COUNTRY) - siae_country.sectors.add(self.sectors[0]) - # tender perimeter custom with include_country_area = False - tender_1 = TenderFactory(sectors=self.sectors, perimeters=self.perimeters) - siae_found_list = Siae.objects.filter_with_tender(tender_1) - self.assertEqual(len(siae_found_list), 2 + 0) - # tender perimeter custom with include_country_area = True - tender_2 = TenderFactory(sectors=self.sectors, perimeters=self.perimeters, include_country_area=True) - siae_found_list = Siae.objects.filter_with_tender(tender_2) - self.assertEqual(len(siae_found_list), 2 + 1) - - def test_matching_siae_country(self): - # add Siae with geo_range_country - siae_country = SiaeFactory(is_active=True, geo_range=siae_constants.GEO_RANGE_COUNTRY) - siae_country_2 = SiaeFactory(is_active=True, geo_range=siae_constants.GEO_RANGE_COUNTRY) - siae_country.sectors.add(self.sectors[0]) - siae_country_2.sectors.add(self.sectors[0]) - # tender perimeter custom with is_country_area = False - tender_1 = TenderFactory(sectors=self.sectors, is_country_area=True) - siae_found_list_1 = Siae.objects.filter_with_tender(tender_1) - self.assertEqual(len(siae_found_list_1), 2) - # tender perimeter custom with include_country_area = True - tender_2 = TenderFactory(sectors=self.sectors, include_country_area=True) - siae_found_list_2 = Siae.objects.filter_with_tender(tender_2) - # we should have the same length of structures - self.assertEqual(len(siae_found_list_1), len(siae_found_list_2)) - # add perimeters - tender_2.perimeters.set(self.perimeters) - siae_found_list_2 = Siae.objects.filter_with_tender(tender_2) - self.assertEqual(len(siae_found_list_2), 2 + 2) - tender_2.is_country_area = True - tender_2.save() - siae_found_list_2 = Siae.objects.filter_with_tender(tender_2) - # we should have only siaes with country geo range - self.assertEqual(len(siae_found_list_2), 2 + 0) - - def test_matching_siae_perimeters_custom_2(self): - # add Siae with geo_range_department (75) - siae_department = SiaeFactory(is_active=True, department="75", geo_range=siae_constants.GEO_RANGE_DEPARTMENT) - siae_department.sectors.add(self.sectors[0]) - # tender perimeter custom - tender = TenderFactory(sectors=self.sectors, perimeters=self.perimeters) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 2 + 1) - - def test_matching_siae_perimeters_france(self): - # tender france - tender = TenderFactory(sectors=self.sectors, is_country_area=True) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 0) - # add Siae with geo_range_country - siae_country = SiaeFactory(is_active=True, geo_range=siae_constants.GEO_RANGE_COUNTRY) - siae_country.sectors.add(self.sectors[0]) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 1) - - def test_no_siaes(self): - # tender with empty sectors list - tender = TenderFactory(sectors=[SectorFactory()], perimeters=self.perimeters) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 0) - # tender near Marseille - tender_marseille = TenderFactory(sectors=self.sectors, perimeters=[self.perimeter_marseille]) - siae_found_list_marseille = Siae.objects.filter_with_tender(tender_marseille) - self.assertEqual(len(siae_found_list_marseille), 0) - - def test_with_no_contact_email(self): - tender = TenderFactory(sectors=self.sectors, perimeters=self.perimeters) - SiaeFactory( - is_active=True, geo_range=siae_constants.GEO_RANGE_COUNTRY, contact_email="", sectors=[self.sectors[0]] - ) - siae_found_list = Siae.objects.filter_with_tender(tender) - self.assertEqual(len(siae_found_list), 2 + 0) - - # def test_number_queries(self): - # tender = TenderFactory(sectors=self.sectors) - # with self.assertNumQueries(8): - # siae_found_list = Siae.objects.filter_with_tender(tender) - # self.assertEqual(len(siae_found_list), 2) - - class TenderListViewTest(TestCase): @classmethod def setUpTestData(cls):