diff --git a/qfdmo/admin/acteur.py b/qfdmo/admin/acteur.py index 8d170d84d..5f2db4fbf 100644 --- a/qfdmo/admin/acteur.py +++ b/qfdmo/admin/acteur.py @@ -35,6 +35,7 @@ DisplayedActeur, DisplayedPropositionService, LabelQualite, + ToutActeur, ) from qfdmo.models.categorie_objet import CategorieObjet from qfdmo.widgets import CustomOSMWidget @@ -811,6 +812,10 @@ def get_readonly_fields(self, request, obj=None): return [] +class ToutActeurAdmin(NotEditableInlineMixin, BaseActeurAdmin): + inlines = [] + + admin.site.register(Acteur, ActeurAdmin) admin.site.register(ActeurService, CodeLibelleModelAdmin) admin.site.register(ActeurType, CodeLibelleModelAdmin) @@ -820,3 +825,4 @@ def get_readonly_fields(self, request, obj=None): admin.site.register(RevisionPropositionService, RevisionPropositionServiceAdmin) admin.site.register(Source, CodeLibelleModelAdmin) admin.site.register(LabelQualite, CodeLibelleModelAdmin) +admin.site.register(ToutActeur, ToutActeurAdmin) diff --git a/qfdmo/migrations/0118_toutacteur.py b/qfdmo/migrations/0118_toutacteur.py new file mode 100644 index 000000000..b9b799a75 --- /dev/null +++ b/qfdmo/migrations/0118_toutacteur.py @@ -0,0 +1,145 @@ +# Generated by Django 5.1.4 on 2025-02-03 13:47 + +import django.contrib.gis.db.models.fields +import django.db.models.functions.datetime +from django.db import migrations, models + +import qfdmo.models.acteur + + +class Migration(migrations.Migration): + + dependencies = [ + ("qfdmo", "0117_update_empty_url"), + ] + + operations = [ + migrations.CreateModel( + name="ToutActeur", + fields=[ + ( + "cree_le", + models.DateTimeField( + auto_now_add=True, + db_default=django.db.models.functions.datetime.Now(), + ), + ), + ( + "modifie_le", + models.DateTimeField( + auto_now=True, + db_default=django.db.models.functions.datetime.Now(), + ), + ), + ("nom", models.CharField(max_length=255)), + ("description", models.TextField(blank=True, null=True)), + ( + "identifiant_unique", + models.CharField( + blank=True, + max_length=255, + primary_key=True, + serialize=False, + unique=True, + ), + ), + ("adresse", models.CharField(blank=True, max_length=255, null=True)), + ( + "adresse_complement", + models.CharField(blank=True, max_length=255, null=True), + ), + ("code_postal", models.CharField(blank=True, max_length=10, null=True)), + ("ville", models.CharField(blank=True, max_length=255, null=True)), + ("url", models.CharField(blank=True, max_length=2048, null=True)), + ("email", models.EmailField(blank=True, max_length=254, null=True)), + ( + "location", + django.contrib.gis.db.models.fields.PointField( + blank=True, null=True, srid=4326 + ), + ), + ("telephone", models.CharField(blank=True, max_length=255, null=True)), + ( + "nom_commercial", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "nom_officiel", + models.CharField(blank=True, max_length=255, null=True), + ), + ("siren", models.CharField(blank=True, max_length=9, null=True)), + ("siret", models.CharField(blank=True, max_length=14, null=True)), + ( + "identifiant_externe", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "statut", + models.CharField( + choices=[ + ("ACTIF", "actif"), + ("INACTIF", "inactif"), + ("SUPPRIME", "supprimé"), + ], + db_default="ACTIF", + default="ACTIF", + max_length=255, + ), + ), + ( + "naf_principal", + models.CharField(blank=True, max_length=255, null=True), + ), + ("commentaires", models.TextField(blank=True, null=True)), + ( + "horaires_osm", + models.CharField( + blank=True, + null=True, + validators=[qfdmo.models.acteur.validate_opening_hours], + ), + ), + ("horaires_description", models.TextField(blank=True, null=True)), + ( + "public_accueilli", + models.CharField( + blank=True, + choices=[ + ( + "Particuliers et professionnels", + "Particuliers et professionnels", + ), + ("Professionnels", "Professionnels"), + ("Particuliers", "Particuliers"), + ("Aucun", "Aucun"), + ], + max_length=255, + null=True, + ), + ), + ( + "reprise", + models.CharField( + blank=True, + choices=[("1 pour 0", "1 pour 0"), ("1 pour 1", "1 pour 1")], + max_length=255, + null=True, + ), + ), + ( + "exclusivite_de_reprisereparation", + models.BooleanField( + blank=True, + null=True, + verbose_name="Exclusivité de reprise/réparation", + ), + ), + ("uniquement_sur_rdv", models.BooleanField(blank=True, null=True)), + ], + options={ + "verbose_name": "ACTEUR de l'EC - AFFICHÉ", + "verbose_name_plural": "ACTEURS de l'EC - AFFICHÉ", + "managed": False, + }, + ), + ] diff --git a/qfdmo/migrations/0119_toutacteur_views.py b/qfdmo/migrations/0119_toutacteur_views.py new file mode 100644 index 000000000..73612245f --- /dev/null +++ b/qfdmo/migrations/0119_toutacteur_views.py @@ -0,0 +1,123 @@ +# Generated by Django 5.1.4 on 2025-02-03 13:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("qfdmo", "0118_toutacteur"), + ] + + operations = [ + migrations.RunSQL( + """ +DROP VIEW IF EXISTS qfdmo_toutacteur; + +CREATE VIEW qfdmo_toutacteur AS ( + SELECT + a.identifiant_unique AS identifiant_unique, + COALESCE(ra.nom, a.nom) AS nom, + COALESCE(ra.description, a.description) AS description, + COALESCE(ra.acteur_type_id, a.acteur_type_id) AS acteur_type_id, + COALESCE(ra.adresse, a.adresse) AS adresse, + COALESCE(ra.adresse_complement, a.adresse_complement) AS adresse_complement, + COALESCE(ra.code_postal, a.code_postal) AS code_postal, + COALESCE(ra.ville, a.ville) AS ville, + COALESCE(ra.url, a.url) AS url, + COALESCE(ra.email, a.email) AS email, + COALESCE(ra.location, a.location) AS location, + COALESCE(ra.telephone, a.telephone) AS telephone, + COALESCE(ra.nom_commercial, a.nom_commercial) AS nom_commercial, + COALESCE(ra.nom_officiel, a.nom_officiel) AS nom_officiel, + COALESCE(ra.siren, a.siren) AS siren, + COALESCE(ra.siret, a.siret) AS siret, + COALESCE(ra.source_id, a.source_id) AS source_id, + COALESCE(ra.identifiant_externe, a.identifiant_externe) AS identifiant_externe, + COALESCE(ra.statut, a.statut) AS statut, + COALESCE(ra.naf_principal, a.naf_principal) AS naf_principal, + COALESCE(ra.commentaires, a.commentaires) AS commentaires, + COALESCE(ra.horaires_osm, a.horaires_osm) AS horaires_osm, + COALESCE(ra.horaires_description, a.horaires_description) AS horaires_description, + COALESCE(ra.public_accueilli, a.public_accueilli) AS public_accueilli, + COALESCE(ra.reprise, a.reprise) AS reprise, + COALESCE(ra.exclusivite_de_reprisereparation, a.exclusivite_de_reprisereparation) AS exclusivite_de_reprisereparation, + COALESCE(ra.uniquement_sur_rdv, a.uniquement_sur_rdv) AS uniquement_sur_rdv, + COALESCE(ra.action_principale_id, a.action_principale_id) AS action_principale_id, + a.cree_le AS cree_le, + COALESCE(ra.modifie_le, a.modifie_le) AS modifie_le, + ra.parent_id AS parent_id + --proposition_services + FROM qfdmo_acteur AS a + LEFT OUTER JOIN qfdmo_revisionacteur AS ra + ON a.identifiant_unique = ra.identifiant_unique +); +""", + reverse_sql=""" +DROP VIEW IF EXISTS qfdmo_toutacteur; +""", + ), + migrations.RunSQL( + """ +DROP VIEW IF EXISTS qfdmo_toutacteur_labels; + +-- Créer la vue +CREATE VIEW qfdmo_toutacteur_labels AS ( + SELECT + CASE + WHEN ra.identifiant_unique IS NULL THEN al.id + ELSE ral.id + END AS id, + CASE + WHEN ra.identifiant_unique IS NOT NULL THEN ral.revisionacteur_id + ELSE al.acteur_id + END AS toutacteur_id, + CASE + WHEN ra.identifiant_unique IS NOT NULL THEN ral.labelqualite_id + ELSE al.labelqualite_id + END AS labelqualite_id + FROM qfdmo_acteur_labels AS al + full join qfdmo_revisionacteur_labels AS ral on al.acteur_id = ral.revisionacteur_id + left join qfdmo_revisionacteur AS ra on al.acteur_id = ra.identifiant_unique + where CASE + WHEN ra.identifiant_unique IS NULL THEN al.id + ELSE ral.id + END is not null +); +""", + reverse_sql=""" +DROP VIEW IF EXISTS qfdmo_toutacteur_labels; +""", + ), + migrations.RunSQL( + """ +DROP VIEW IF EXISTS qfdmo_toutacteur_acteur_services; + +CREATE VIEW qfdmo_toutacteur_acteur_services AS ( + SELECT + CASE + WHEN ra.identifiant_unique IS NULL THEN aas.id + ELSE raas.id + END AS id, + CASE + WHEN ra.identifiant_unique IS NOT NULL THEN raas.revisionacteur_id + ELSE aas.acteur_id + END AS toutacteur_id, + CASE + WHEN ra.identifiant_unique IS NOT NULL THEN raas.acteurservice_id + ELSE aas.acteurservice_id + END AS acteurservice_id + FROM qfdmo_acteur_acteur_services AS aas + full join qfdmo_revisionacteur_acteur_services AS raas on aas.acteur_id = raas.revisionacteur_id + left join qfdmo_revisionacteur AS ra on aas.acteur_id = ra.identifiant_unique + where CASE + WHEN ra.identifiant_unique IS NULL THEN aas.id + ELSE raas.id + END is not null +); +""", + reverse_sql=""" +DROP VIEW IF EXISTS qfdmo_toutacteur_acteur_services; +""", + ), + ] diff --git a/qfdmo/models/acteur.py b/qfdmo/models/acteur.py index 94ce96302..e678b50dc 100644 --- a/qfdmo/models/acteur.py +++ b/qfdmo/models/acteur.py @@ -798,6 +798,30 @@ def __str__(self): ) +""" +Model to display all acteurs in admin +""" + + +class ToutActeur(BaseActeur): + + parent = models.ForeignKey( + "self", + verbose_name="Dédupliqué par", + help_text="Acteur «chapeau» utilisé pour dédupliquer cet acteur", + on_delete=models.SET_NULL, + blank=True, + null=True, + related_name="duplicats", + validators=[clean_parent], + ) + + class Meta: + managed = False + verbose_name = "Vue sur l'acteur" + verbose_name_plural = "Vues sur tous les acteurs" + + class DisplayedActeur(BaseActeur): objects = DisplayedActeurManager()