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

[Dépôt de besoin] Ciblage par rayon en KM #999

Merged
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
18 changes: 15 additions & 3 deletions lemarche/siaes/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,12 @@ def geo_range_in_perimeter_list(self, perimeters: models.QuerySet, with_country=
conditions = Q(geo_range=siae_constants.GEO_RANGE_COUNTRY) | conditions
return self.filter(conditions)

def within(self, point, distance_km=0):
return self.filter(coords__dwithin=(point, D(km=distance_km)))
def within(self, point, distance_km=0, include_country_area=False):
SebastienReuiller marked this conversation as resolved.
Show resolved Hide resolved
return (
self.filter(Q(coords__dwithin=(point, D(km=distance_km))) | Q(geo_range=siae_constants.GEO_RANGE_COUNTRY))
if include_country_area
else self.filter(coords__dwithin=(point, D(km=distance_km)))
)

def with_country_geo_range(self):
return self.filter(Q(geo_range=siae_constants.GEO_RANGE_COUNTRY))
Expand Down Expand Up @@ -346,7 +350,15 @@ def filter_with_tender(self, tender, tender_status=None): # noqa C901
if tender.is_country_area: # for all country
qs = qs.with_country_geo_range()
else:
if tender.perimeters.count() and tender.include_country_area: # perimeters and all country
# 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
)
Expand Down
1 change: 1 addition & 0 deletions lemarche/tenders/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ class TenderAdmin(FieldsetsInlineMixin, admin.ModelAdmin):
"fields": (
"location",
"perimeters",
"distance_location",
"include_country_area",
),
},
Expand Down
22 changes: 22 additions & 0 deletions lemarche/tenders/migrations/0063_tender_distance_location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 4.2.2 on 2023-11-30 06:28

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("tenders", "0062_tender_rename_kind_quote_display"),
]

operations = [
migrations.AddField(
model_name="tender",
name="distance_location",
field=models.IntegerField(
blank=True,
help_text="Si vous décidez de faire un ciblage en km, vérifiez que le lieu d’intervention est bien renseigné et est une ville et que le kilométrage indiqué correspond à la réalité du besoin et des prestataires en face", # noqa
null=True,
verbose_name="Distance en kilomètres autour du lieu d'intervention",
),
),
]
9 changes: 9 additions & 0 deletions lemarche/tenders/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,15 @@ class Tender(models.Model):
blank=True,
help_text="Ajoutez un ou plusieurs lieux d'exécutions",
)
distance_location = models.IntegerField(
verbose_name="Distance en kilomètres autour du lieu d'intervention",
blank=True,
null=True,
help_text=(
"Si vous décidez de faire un ciblage en km, vérifiez que le lieu d’intervention est bien renseigné et est "
"une ville et que le kilométrage indiqué correspond à la réalité du besoin et des prestataires en face"
),
)
include_country_area = models.BooleanField(
verbose_name="Inclure les structures qui ont comme périmètre d'intervention 'France entière' ?",
help_text="Laisser vide pour exclure les structures qui ont comme périmètre d'intervention 'France entière'",
Expand Down
92 changes: 88 additions & 4 deletions lemarche/www/tenders/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,15 +235,15 @@ def setUpTestData(cls):
# by default is Paris
coords_paris = Point(48.86385199985207, 2.337071483848432)

siae_one = SiaeFactory(
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,
)
siae_two = SiaeFactory(
cls.siae_two = SiaeFactory(
is_active=True,
kind=siae_constants.KIND_ESAT,
presta_type=[siae_constants.PRESTA_BUILD],
Expand All @@ -252,8 +252,8 @@ def setUpTestData(cls):
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])
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)
Expand Down Expand Up @@ -293,6 +293,90 @@ def test_matching_siae_sectors(self):
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))
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)
Expand Down