From e45885119af12a5af20ef22fdcfd2a0099a00f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Odini?= Date: Thu, 13 Jun 2024 11:15:01 +0200 Subject: [PATCH] =?UTF-8?q?feat(Structures):=20Nouveau=20mod=C3=A8le=20Sia?= =?UTF-8?q?eActivity=20(#1261)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lemarche/siaes/admin.py | 103 ++++++++++++++-- lemarche/siaes/factories.py | 22 +++- .../siaes/migrations/0076_siaeactivity.py | 112 ++++++++++++++++++ lemarche/siaes/models.py | 51 ++++++++ lemarche/tenders/admin.py | 5 +- 5 files changed, 277 insertions(+), 16 deletions(-) create mode 100644 lemarche/siaes/migrations/0076_siaeactivity.py diff --git a/lemarche/siaes/admin.py b/lemarche/siaes/admin.py index dd0b4537d..4fbdc2709 100644 --- a/lemarche/siaes/admin.py +++ b/lemarche/siaes/admin.py @@ -11,10 +11,12 @@ from lemarche.conversations.models import Conversation from lemarche.labels.models import Label +from lemarche.networks.models import Network from lemarche.notes.models import Note from lemarche.siaes import constants as siae_constants from lemarche.siaes.models import ( Siae, + SiaeActivity, SiaeClientReference, SiaeGroup, SiaeImage, @@ -66,6 +68,26 @@ def queryset(self, request, queryset): return queryset +class SiaeActivityInline(admin.TabularInline): + model = SiaeActivity + fields = [ + "sector_group", + "sectors", + "presta_type", + "location", + "geo_range", + "geo_range_custom_distance", + "created_at", + ] + show_change_link = True + can_delete = False + extra = 0 + max_num = 0 + + def has_change_permission(self, request, obj=None): + return False + + class SiaeLabelInline(admin.TabularInline): model = SiaeLabel fields = ["label", "label_with_link", "created_at", "updated_at"] @@ -107,12 +129,11 @@ def user_with_link(self, siae_user): class ConversationsInline(admin.TabularInline): model = Conversation - can_delete = False - fields = ["id", "title_with_link", "nb_message_with_link", "kind", "created_at"] - # autocomplete_fields = ["user"] readonly_fields = ["id", "title_with_link", "nb_message_with_link", "kind", "created_at"] + show_change_link = True + can_delete = False extra = 0 max_num = 0 @@ -144,6 +165,7 @@ class SiaeAdmin(FieldsetsInlineMixin, gis_admin.OSMGeoAdmin, SimpleHistoryAdmin) "tender_detail_display_count_annotated_with_link", "tender_detail_contact_click_count_annotated_with_link", "tender_detail_not_interested_count_annotated_with_link", + "activity_count_with_link", "offer_count_with_link", "label_count_with_link", "client_reference_count_with_link", @@ -175,6 +197,7 @@ class SiaeAdmin(FieldsetsInlineMixin, gis_admin.OSMGeoAdmin, SimpleHistoryAdmin) "sector_count_with_link", "network_count_with_link", "group_count_with_link", + "activity_count_with_link", "offer_count_with_link", "label_count_with_link", "client_reference_count_with_link", @@ -253,10 +276,7 @@ class SiaeAdmin(FieldsetsInlineMixin, gis_admin.OSMGeoAdmin, SimpleHistoryAdmin) "description", "sectors", "sector_count_with_link", - "networks", - "network_count_with_link", - "groups", - "group_count_with_link", + "activity_count_with_link", "offer_count_with_link", "label_count_with_link", "client_reference_count_with_link", @@ -265,6 +285,7 @@ class SiaeAdmin(FieldsetsInlineMixin, gis_admin.OSMGeoAdmin, SimpleHistoryAdmin) ) }, ), + SiaeActivityInline, SiaeLabelInline, ( "Périmètre d'intervention", @@ -289,6 +310,17 @@ class SiaeAdmin(FieldsetsInlineMixin, gis_admin.OSMGeoAdmin, SimpleHistoryAdmin) ) }, ), + ( + "Réseaux & Groupements", + { + "fields": ( + "networks", + "network_count_with_link", + "groups", + "group_count_with_link", + ) + }, + ), SiaeNoteInline, ConversationsInline, SiaeUserInline, @@ -488,7 +520,7 @@ def user_count_with_link(self, siae): url = reverse("admin:users_user_changelist") + f"?siaes__in={siae.id}" return format_html(f'{siae.user_count}') - user_count_with_link.short_description = "Utilisateurs" + user_count_with_link.short_description = User._meta.verbose_name_plural user_count_with_link.admin_order_field = "user_count" def sector_count_with_link(self, siae): @@ -502,21 +534,28 @@ def network_count_with_link(self, siae): url = reverse("admin:networks_network_changelist") + f"?siaes__in={siae.id}" return format_html(f'{siae.network_count}') - network_count_with_link.short_description = "Réseaux" + network_count_with_link.short_description = Network._meta.verbose_name_plural network_count_with_link.admin_order_field = "network_count" def group_count_with_link(self, siae): url = reverse("admin:siaes_siaegroup_changelist") + f"?siaes__in={siae.id}" return format_html(f'{siae.group_count}') - group_count_with_link.short_description = "Groupements" + group_count_with_link.short_description = SiaeGroup._meta.verbose_name_plural group_count_with_link.admin_order_field = "group_count" + def activity_count_with_link(self, siae): + url = reverse("admin:siaes_siaeactivity_changelist") + f"?siae__id__exact={siae.id}" + return format_html(f'{siae.offer_count}') + + activity_count_with_link.short_description = SiaeActivity._meta.verbose_name_plural + activity_count_with_link.admin_order_field = "offer_count" + def offer_count_with_link(self, siae): url = reverse("admin:siaes_siaeoffer_changelist") + f"?siae__id__exact={siae.id}" return format_html(f'{siae.offer_count}') - offer_count_with_link.short_description = "Prestations" + offer_count_with_link.short_description = SiaeOffer._meta.verbose_name_plural offer_count_with_link.admin_order_field = "offer_count" def label_count_with_link(self, siae): @@ -537,7 +576,7 @@ def image_count_with_link(self, siae): url = reverse("admin:siaes_siaeimage_changelist") + f"?siae__id__exact={siae.id}" return format_html(f'{siae.image_count}') - image_count_with_link.short_description = "Images" + image_count_with_link.short_description = SiaeImage._meta.verbose_name_plural image_count_with_link.admin_order_field = "image_count" def coords_display(self, siae): @@ -678,6 +717,46 @@ def has_delete_permission(self, request, obj=None): return False +@admin.register(SiaeActivity, site=admin_site) +class SiaeActivityAdmin(admin.ModelAdmin): + list_display = ["id", "siae_with_link", "sector_group", "created_at"] + list_filter = ["sectors"] + search_fields = ["id", "siae__id", "siae__name"] + search_help_text = "Cherche sur les champs : ID, Structure (ID, Nom)" + + autocomplete_fields = ["siae", "sectors", "location"] + readonly_fields = ["created_at", "updated_at"] + + fieldsets = ( + ( + "Structure", + { + "fields": ("siae",), + }, + ), + ( + "Prestation", + { + "fields": ("sector_group", "sectors", "presta_type"), + }, + ), + ( + "Localisation et périmètre d'intervention", + { + "fields": ("location", "geo_range", "geo_range_custom_distance"), + }, + ), + ("Dates", {"fields": ("created_at", "updated_at")}), + ) + + def siae_with_link(self, siae_offer): + url = reverse("admin:siaes_siae_change", args=[siae_offer.siae_id]) + return format_html(f'{siae_offer.siae}') + + siae_with_link.short_description = Siae._meta.verbose_name + siae_with_link.admin_order_field = "siae" + + @admin.register(SiaeOffer, site=admin_site) class SiaeOfferAdmin(admin.ModelAdmin): list_display = ["id", "name", "siae_with_link", "source", "created_at"] diff --git a/lemarche/siaes/factories.py b/lemarche/siaes/factories.py index a8fc9c81f..46d19cc55 100644 --- a/lemarche/siaes/factories.py +++ b/lemarche/siaes/factories.py @@ -4,7 +4,15 @@ from factory.django import DjangoModelFactory from lemarche.siaes import constants as siae_constants -from lemarche.siaes.models import Siae, SiaeClientReference, SiaeGroup, SiaeImage, SiaeLabelOld, SiaeOffer +from lemarche.siaes.models import ( + Siae, + SiaeActivity, + SiaeClientReference, + SiaeGroup, + SiaeImage, + SiaeLabelOld, + SiaeOffer, +) class SiaeGroupFactory(DjangoModelFactory): @@ -57,6 +65,18 @@ def networks(self, create, extracted, **kwargs): self.networks.add(*extracted) +class SiaeActivityFactory(DjangoModelFactory): + class Meta: + model = SiaeActivity + + presta_type = factory.List([factory.fuzzy.FuzzyChoice([key for (key, value) in siae_constants.PRESTA_CHOICES])]) + + @factory.post_generation + def sectors(self, create, extracted, **kwargs): + if extracted: + self.sectors.add(*extracted) + + class SiaeOfferFactory(DjangoModelFactory): class Meta: model = SiaeOffer diff --git a/lemarche/siaes/migrations/0076_siaeactivity.py b/lemarche/siaes/migrations/0076_siaeactivity.py new file mode 100644 index 000000000..a13371865 --- /dev/null +++ b/lemarche/siaes/migrations/0076_siaeactivity.py @@ -0,0 +1,112 @@ +# Generated by Django 4.2.13 on 2024-06-10 15:00 + +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + +import lemarche.utils.fields + + +class Migration(migrations.Migration): + dependencies = [ + ("sectors", "0003_sector_sectorgroup_ordering"), + ("perimeters", "0005_alter_perimeter_post_codes"), + ("siaes", "0075_historicalsiae"), + ] + + operations = [ + migrations.CreateModel( + name="SiaeActivity", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "presta_type", + lemarche.utils.fields.ChoiceArrayField( + base_field=models.CharField( + choices=[ + ("DISP", "Mise à disposition - Interim"), + ("PREST", "Prestation de service"), + ("BUILD", "Fabrication et commercialisation de biens"), + ], + max_length=20, + ), + blank=True, + db_index=True, + null=True, + size=None, + verbose_name="Type de prestation", + ), + ), + ( + "geo_range", + models.CharField( + blank=True, + choices=[ + ("COUNTRY", "France entière"), + ("REGION", "Région"), + ("DEPARTMENT", "Département"), + ("CUSTOM", "Distance en kilomètres"), + ], + db_index=True, + max_length=20, + verbose_name="Périmètre d'intervention", + ), + ), + ( + "geo_range_custom_distance", + models.IntegerField( + blank=True, null=True, verbose_name="Distance en kilomètres (périmètre d'intervention)" + ), + ), + ( + "created_at", + models.DateTimeField(default=django.utils.timezone.now, verbose_name="Date de création"), + ), + ("updated_at", models.DateTimeField(auto_now=True, verbose_name="Date de modification")), + ( + "sectors", + models.ManyToManyField( + blank=True, + related_name="siae_activities", + to="sectors.sector", + verbose_name="Activités", + ), + ), + ( + "siae", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="activities", + to="siaes.siae", + verbose_name="Structure", + ), + ), + ( + "sector_group", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="siae_activities", + to="sectors.sectorgroup", + verbose_name="Secteur d'activité", + ), + ), + ( + "location", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="siae_activities", + to="perimeters.perimeter", + verbose_name="Localisation", + ), + ), + ], + options={ + "verbose_name": "Activité", + "verbose_name_plural": "Activités", + "ordering": ["-created_at"], + }, + ), + ] diff --git a/lemarche/siaes/models.py b/lemarche/siaes/models.py index 04c93cb7a..b7ceb2dc2 100644 --- a/lemarche/siaes/models.py +++ b/lemarche/siaes/models.py @@ -1393,6 +1393,57 @@ class Meta: ordering = ["-created_at"] +class SiaeActivity(models.Model): + siae = models.ForeignKey( + "siaes.Siae", verbose_name="Structure", related_name="activities", on_delete=models.CASCADE + ) + + sector_group = models.ForeignKey( + "sectors.SectorGroup", + verbose_name="Secteur d'activité", + related_name="siae_activities", + on_delete=models.SET_NULL, + null=True, + # blank=True, + ) + sectors = models.ManyToManyField( + "sectors.Sector", verbose_name="Activités", related_name="siae_activities", blank=True + ) + presta_type = ChoiceArrayField( + verbose_name="Type de prestation", + base_field=models.CharField(max_length=20, choices=siae_constants.PRESTA_CHOICES), + blank=True, + null=True, + db_index=True, + ) + location: Perimeter = models.ForeignKey( + to="perimeters.Perimeter", + verbose_name="Localisation", + related_name="siae_activities", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + ) + geo_range = models.CharField( + verbose_name="Périmètre d'intervention", + max_length=20, + choices=siae_constants.GEO_RANGE_CHOICES, + blank=True, + db_index=True, + ) + geo_range_custom_distance = models.IntegerField( + verbose_name="Distance en kilomètres (périmètre d'intervention)", blank=True, null=True + ) + + created_at = models.DateTimeField(verbose_name="Date de création", default=timezone.now) + updated_at = models.DateTimeField(verbose_name="Date de modification", auto_now=True) + + class Meta: + verbose_name = "Activité" + verbose_name_plural = "Activités" + ordering = ["-created_at"] + + class SiaeOffer(models.Model): name = models.CharField(verbose_name="Nom", max_length=255) description = models.TextField(verbose_name="Description", blank=True) diff --git a/lemarche/tenders/admin.py b/lemarche/tenders/admin.py index 1d9279fda..31472276b 100644 --- a/lemarche/tenders/admin.py +++ b/lemarche/tenders/admin.py @@ -164,9 +164,9 @@ class TenderSiaeInterestedInline(admin.TabularInline): "transactioned", ] readonly_fields = [field.name for field in TenderSiae._meta.fields if field.name not in ["transactioned"]] - extra = 0 show_change_link = True can_delete = False + extra = 0 classes = ["collapse"] def has_add_permission(self, request, obj=None): @@ -191,10 +191,9 @@ class TenderSiaeUserInline(admin.TabularInline): "user_full_name", "user_phone", ] - - extra = 0 show_change_link = True can_delete = False + extra = 0 classes = ["collapse"] def has_add_permission(self, request, obj=None):