diff --git a/loko/assets/admin.py b/loko/assets/admin.py index a5b2233..7ff1215 100644 --- a/loko/assets/admin.py +++ b/loko/assets/admin.py @@ -451,7 +451,10 @@ admin.site.register(ComplementaryPoliceRegulation, AssetAdmin) admin.site.register(SignParkingSpot, AssetAdmin) -from .models import ITSAssetModel, ITSGeoAsset, ITSGeoAssetContract +from .models import ( + ITSAssetModel, ITSGeoAsset, ITSGeoAssetContract, + ITSBollardSystem, ITSBollardPlot, ITSBollardSystemAsset, ITSBollardSystemContract +) admin.site.register(ITSAssetModel, AssetAdmin) @@ -468,6 +471,44 @@ class ITSGeoAssetContractAdmin(AssetAdmin): list_filter = ("contract",) +class ITSBollardPlotInline(admin.TabularInline): + model = ITSBollardPlot + extra = 0 + fields = ("code", "position", "bollard_type", "model", "serial_number", "status") + show_change_link = True + + +class ITSBollardSystemAssetInline(admin.TabularInline): + model = ITSBollardSystemAsset + extra = 0 + fields = ("asset", "role", "comment") + autocomplete_fields = ("asset",) + show_change_link = True + + +@admin.register(ITSBollardSystem) +class ITSBollardSystemAdmin(AssetGISAdmin, AssetAdmin): + search_fields = ("code", "name_fr", "name_nl", "location__code", "location__name_fr") + list_display = ("code", "name_fr", "location", "width", "direction", "status", "plots_count") + list_filter = ("status", "direction", "location") + inlines = [ITSBollardPlotInline, ITSBollardSystemAssetInline] + + +@admin.register(ITSBollardPlot) +class ITSBollardPlotAdmin(AssetGISAdmin, AssetAdmin): + search_fields = ("code", "name_fr", "system__code", "serial_number") + list_display = ("code", "system", "position", "bollard_type", "model", "status") + list_filter = ("bollard_type", "status", "system") + + +@admin.register(ITSBollardSystemContract) +class ITSBollardSystemContractAdmin(AssetAdmin): + search_fields = ("system__code", "contract__contract_number") + list_display = ("system", "contract", "status", "maintenance_type") + list_filter = ("status", "maintenance_type", "contract") + + + from .models import ITSLocation, ITSLocationDocument, ITSLocationContract admin.site.register(ITSLocation, AssetAdmin) diff --git a/loko/assets/migrations/0031_itsgeoasset_extra_data_itsbollardsystem_and_more.py b/loko/assets/migrations/0031_itsgeoasset_extra_data_itsbollardsystem_and_more.py new file mode 100644 index 0000000..acc8e93 --- /dev/null +++ b/loko/assets/migrations/0031_itsgeoasset_extra_data_itsbollardsystem_and_more.py @@ -0,0 +1,160 @@ +# Generated by Django 6.0.7 on 2026-10-08 10:19 + +import assets.models.core +import django.contrib.gis.db.models.fields +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0030_managementzone_managementzonedocument_and_more'), + ('contracts', '0014_contract_color_contract_geom'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='itsgeoasset', + name='extra_data', + field=models.JSONField(blank=True, default=dict, verbose_name='Métadonnées techniques'), + ), + migrations.CreateModel( + name='ITSBollardSystem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.CharField(max_length=50)), + ('name_fr', models.CharField(blank=True, max_length=200, null=True)), + ('name_nl', models.CharField(blank=True, max_length=200, null=True)), + ('external_reference', models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe')), + ('status', models.CharField(choices=[('to_be_approved', 'À approuver'), ('planned', 'Planifié'), ('active', 'Actif'), ('damaged', 'Endommagé'), ('to_replace', 'A remplacer'), ('voluntarily_out_of_service', 'Mis hors service volontairement'), ('removed', 'Enlevé'), ('archived', 'Archivé')], default='active', max_length=50)), + ('obsolescence', models.CharField(blank=True, choices=[('good', 'Bon état'), ('medium', 'Moyen'), ('bad', 'Dégradé'), ('critical', 'Critique'), ('unknown', 'Inconnu')], default='unknown', null=True)), + ('installation_date', models.DateTimeField(blank=True, null=True)), + ('uninstallation_date', models.DateTimeField(blank=True, null=True)), + ('serial_number', models.CharField(blank=True, max_length=200, null=True)), + ('warranty_duration', models.IntegerField(blank=True, null=True, verbose_name='Duration of the warranty (months)')), + ('last_inspection_date', models.DateTimeField(blank=True, null=True)), + ('inspection_status', models.CharField(blank=True, choices=[('to_inspect', 'À inspecter'), ('compliant', 'Conforme'), ('non_compliant', 'Non conforme'), ('updated', 'Mis à jour'), ('unknown', 'Non inspecté')], default='unknown', max_length=50, null=True, verbose_name="Statut d'inspection")), + ('last_preventive_date', models.DateTimeField(blank=True, null=True)), + ('last_corrective_date', models.DateTimeField(blank=True, null=True)), + ('last_survey_date', models.DateTimeField(blank=True, null=True)), + ('brand', models.CharField(blank=True, max_length=500, null=True)), + ('marked_as_removed_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de marquage comme retiré')), + ('note', models.TextField(blank=True, null=True, verbose_name='Note')), + ('validation_status', models.CharField(blank=True, choices=[('to_be_validated', 'À valider'), ('validated', 'Validé'), ('to_be_completed', 'À compléter'), ('rejected', 'Rejeté')], max_length=50, null=True, verbose_name='Statut de validation')), + ('funding_program', models.CharField(blank=True, max_length=200, null=True, verbose_name='Programme de financement / Projet')), + ('updated_at', models.DateTimeField(auto_now=True)), + ('lon', models.FloatField(blank=True, null=True)), + ('lat', models.FloatField(blank=True, null=True)), + ('geojson', models.TextField(blank=True, null=True)), + ('geom', django.contrib.gis.db.models.fields.LineStringField(blank=True, null=True, srid=3812, verbose_name='Géométrie (Ligne)')), + ('width', models.FloatField(blank=True, null=True, verbose_name='Largeur de passage (m)')), + ('direction', models.CharField(blank=True, choices=[('unidirectional', 'Sens unique'), ('bidirectional', 'Double sens')], default='bidirectional', max_length=50, null=True, verbose_name='Sens de circulation')), + ('extra_data', models.JSONField(blank=True, default=dict, verbose_name='Métadonnées techniques')), + ('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.assetcategory')), + ('location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bollard_systems', to='assets.itslocation', verbose_name='Localisation ITS')), + ('marked_as_removed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_marked_as_removed', to=settings.AUTH_USER_MODEL, verbose_name='Marqué comme retiré par')), + ('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.itsassetmodel', verbose_name='Modèle de système')), + ('replaced_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='replaces', to='assets.itsbollardsystem', verbose_name='Remplacé par')), + ('updated_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Mis à jour par')), + ], + options={ + 'verbose_name': 'Système de bornes', + 'verbose_name_plural': 'Systèmes de bornes', + }, + bases=(assets.models.core.GeoSyncMixin, models.Model), + ), + migrations.CreateModel( + name='ITSBollardSystemAsset', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('role', models.CharField(choices=[('camera_entry', 'Caméra entrée (LAPI)'), ('camera_exit', 'Caméra sortie (LAPI)'), ('camera_cctv', 'Caméra de contexte / surveillance'), ('cabinet', 'Armoire électrique / automate'), ('totem', 'Totem de commande'), ('traffic_light', 'Feu de franchissement'), ('loop', 'Boucle de détection'), ('other', 'Autre')], default='other', max_length=50, verbose_name="Rôle de l'équipement")), + ('comment', models.CharField(blank=True, max_length=255, null=True, verbose_name='Commentaire')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name="Date d'association")), + ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bollard_system_links', to='assets.itsgeoasset', verbose_name='Équipement ITS')), + ('bollard_system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='system_assets', to='assets.itsbollardsystem', verbose_name='Système de bornes')), + ], + options={ + 'verbose_name': 'Équipement associé au système de bornes', + 'verbose_name_plural': 'Équipements associés au système de bornes', + 'unique_together': {('bollard_system', 'asset')}, + }, + ), + migrations.AddField( + model_name='itsbollardsystem', + name='associated_assets', + field=models.ManyToManyField(blank=True, related_name='bollard_systems', through='assets.ITSBollardSystemAsset', to='assets.itsgeoasset', verbose_name='Équipements associés'), + ), + migrations.CreateModel( + name='ITSBollardSystemContract', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('active', 'Actif'), ('inactive', 'Inactif'), ('archived', 'Archivé')], default='active', max_length=50)), + ('maintenance_type', models.CharField(choices=[('equipments', 'Equipements'), ('office', 'Bureautique'), ('system', 'Système'), ('other', 'Autre')], default='other', max_length=50)), + ('contract', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bollard_systems_contracts', to='contracts.contract')), + ('system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bollard_systems_contracts', to='assets.itsbollardsystem')), + ], + options={ + 'verbose_name': 'Contrat de système de bornes', + 'verbose_name_plural': 'Contrats de systèmes de bornes', + }, + ), + migrations.CreateModel( + name='ITSBollardPlot', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.CharField(max_length=50)), + ('name_fr', models.CharField(blank=True, max_length=200, null=True)), + ('name_nl', models.CharField(blank=True, max_length=200, null=True)), + ('external_reference', models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe')), + ('status', models.CharField(choices=[('to_be_approved', 'À approuver'), ('planned', 'Planifié'), ('active', 'Actif'), ('damaged', 'Endommagé'), ('to_replace', 'A remplacer'), ('voluntarily_out_of_service', 'Mis hors service volontairement'), ('removed', 'Enlevé'), ('archived', 'Archivé')], default='active', max_length=50)), + ('obsolescence', models.CharField(blank=True, choices=[('good', 'Bon état'), ('medium', 'Moyen'), ('bad', 'Dégradé'), ('critical', 'Critique'), ('unknown', 'Inconnu')], default='unknown', null=True)), + ('installation_date', models.DateTimeField(blank=True, null=True)), + ('uninstallation_date', models.DateTimeField(blank=True, null=True)), + ('serial_number', models.CharField(blank=True, max_length=200, null=True)), + ('warranty_duration', models.IntegerField(blank=True, null=True, verbose_name='Duration of the warranty (months)')), + ('last_inspection_date', models.DateTimeField(blank=True, null=True)), + ('inspection_status', models.CharField(blank=True, choices=[('to_inspect', 'À inspecter'), ('compliant', 'Conforme'), ('non_compliant', 'Non conforme'), ('updated', 'Mis à jour'), ('unknown', 'Non inspecté')], default='unknown', max_length=50, null=True, verbose_name="Statut d'inspection")), + ('last_preventive_date', models.DateTimeField(blank=True, null=True)), + ('last_corrective_date', models.DateTimeField(blank=True, null=True)), + ('last_survey_date', models.DateTimeField(blank=True, null=True)), + ('brand', models.CharField(blank=True, max_length=500, null=True)), + ('marked_as_removed_date', models.DateTimeField(blank=True, null=True, verbose_name='Date de marquage comme retiré')), + ('note', models.TextField(blank=True, null=True, verbose_name='Note')), + ('validation_status', models.CharField(blank=True, choices=[('to_be_validated', 'À valider'), ('validated', 'Validé'), ('to_be_completed', 'À compléter'), ('rejected', 'Rejeté')], max_length=50, null=True, verbose_name='Statut de validation')), + ('funding_program', models.CharField(blank=True, max_length=200, null=True, verbose_name='Programme de financement / Projet')), + ('updated_at', models.DateTimeField(auto_now=True)), + ('lon', models.FloatField(blank=True, null=True)), + ('lat', models.FloatField(blank=True, null=True)), + ('geojson', models.TextField(blank=True, null=True)), + ('geom', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=3812, verbose_name='Géométrie (Point)')), + ('position', models.PositiveSmallIntegerField(default=1, help_text='Ordre de gauche à droite (1, 2, 3...)', verbose_name='Position sur la traverse')), + ('bollard_type', models.CharField(choices=[('automatic', 'Automatique / Escamotable'), ('semi_automatic', 'Semi-automatique'), ('fixed', 'Fixe'), ('removable', 'Amovible')], default='automatic', max_length=50, verbose_name='Type de plot')), + ('extra_data', models.JSONField(blank=True, default=dict, verbose_name='Métadonnées techniques')), + ('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.assetcategory')), + ('location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bollard_plots', to='assets.itslocation', verbose_name='Localisation ITS')), + ('marked_as_removed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_marked_as_removed', to=settings.AUTH_USER_MODEL, verbose_name='Marqué comme retiré par')), + ('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.itsassetmodel', verbose_name='Modèle de plot')), + ('replaced_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='replaces', to='assets.itsbollardplot', verbose_name='Remplacé par')), + ('updated_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Mis à jour par')), + ('system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='plots', to='assets.itsbollardsystem', verbose_name='Système de bornes')), + ], + options={ + 'verbose_name': 'Plot de borne', + 'verbose_name_plural': 'Plots de bornes', + 'ordering': ['system', 'position'], + 'indexes': [models.Index(fields=['code'], name='assets_itsb_code_de6000_idx')], + }, + bases=(assets.models.core.GeoSyncMixin, models.Model), + ), + migrations.AddIndex( + model_name='itsbollardsystem', + index=models.Index(fields=['code'], name='assets_itsb_code_a4c148_idx'), + ), + migrations.AlterUniqueTogether( + name='itsbollardsystemcontract', + unique_together={('system', 'contract')}, + ), + ] diff --git a/loko/assets/models/__init__.py b/loko/assets/models/__init__.py index 74b1cb7..7628757 100644 --- a/loko/assets/models/__init__.py +++ b/loko/assets/models/__init__.py @@ -8,7 +8,7 @@ from .structures import Structure, StructureLocation, StructureGeoAsset, Structu from .controlcenters import ControlCenter, ControlCenterLocation, ControlCenterGeoAsset, ControlCenterAssetModel, ControlCenterDocument, ControlCenterContract, ControlCenterGeoAssetContract, ControlCenterAssetDocument from .sign import SignStreet, SignStreetDocument, SignPole, SignPoleModel, SignPanel, SignPanelModel, SignPanelType, ComplementaryPoliceRegulation, SignParkingSpot from .roads import RoadStreet, RoadStreetDocument, Roadway, RoadwayModel, RoadSidewalk, RoadSidewalkModel, RoadFurniture, RoadFurnitureModel, RoadwayFunction, RoadConcreteBlockSet, RoadDrainModel, RoadDrain -from .its import ITSLocation, ITSLocationDocument, ITSAssetModel, ITSGeoAsset, ITSGeoAssetContract, ITSLocationContract +from .its import ITSLocation, ITSLocationDocument, ITSAssetModel, ITSGeoAsset, ITSGeoAssetContract, ITSLocationContract, ITSBollardSystem, ITSBollardPlot, ITSBollardSystemAsset, ITSBollardSystemContract from .clean import CleanLocation, CleanLitterBinModel, CleanLitterBin, CleanLitterBinPhoto, CleanLitterBinObservation, CleanGlassContainerModel, CleanGlassContainer, CleanLocationContract from .buildings import PublicBuilding, BuildingAsset, BuildingEquipment, PublicBuildingDocument from .outdoor import OutdoorLocation, OutdoorAsset, OutdoorLocationDocument diff --git a/loko/assets/models/its.py b/loko/assets/models/its.py index 23b298e..83a8438 100644 --- a/loko/assets/models/its.py +++ b/loko/assets/models/its.py @@ -5,7 +5,7 @@ from django.contrib.gis.db import models from django.urls import reverse from django.utils.text import slugify -from django.utils.translation import get_language +from django.utils.translation import get_language, gettext_lazy as _ import os @@ -91,6 +91,7 @@ class ITSGeoAsset(AbstractGeoAsset): location = models.ForeignKey(ITSLocation, related_name="assets", on_delete=models.SET_NULL, null=True, blank=True) trafficlights_pole = models.ForeignKey("assets.TrafficLightPole", on_delete=models.SET_NULL, null=True, blank=True, related_name="its_assets", verbose_name="Traffic Light Pole") lighting_pole = models.ForeignKey("assets.PublicLightingPole", on_delete=models.SET_NULL, null=True, blank=True, related_name="its_assets", verbose_name="Lighting Pole") + extra_data = models.JSONField(default=dict, blank=True, verbose_name=_("Métadonnées techniques")) def clean(self): super().clean() @@ -117,6 +118,147 @@ class ITSGeoAsset(AbstractGeoAsset): def get_absolute_url(self): return reverse('assets:its_assets_detail', kwargs={'asset_model': 'itsgeoasset', 'asset_id':self.id}) + + +BOLLARD_DIRECTION_CHOICES = ( + ('unidirectional', _('Sens unique')), + ('bidirectional', _('Double sens')), +) + +class ITSBollardSystem(AbstractGeoAsset): + """ + Système / Traverse de bornes escamotables (représentation linéaire). + Contrôle l'accès à une voie ou zone piétonne. + """ + geom = models.LineStringField(srid=3812, null=True, blank=True, verbose_name=_("Géométrie (Ligne)")) + model = models.ForeignKey(ITSAssetModel, on_delete=models.PROTECT, null=True, blank=True, verbose_name=_("Modèle de système")) + location = models.ForeignKey(ITSLocation, related_name="bollard_systems", on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Localisation ITS")) + width = models.FloatField(null=True, blank=True, verbose_name=_("Largeur de passage (m)")) + direction = models.CharField(max_length=50, choices=BOLLARD_DIRECTION_CHOICES, default='bidirectional', null=True, blank=True, verbose_name=_("Sens de circulation")) + extra_data = models.JSONField(default=dict, blank=True, verbose_name=_("Métadonnées techniques")) + associated_assets = models.ManyToManyField( + 'assets.ITSGeoAsset', + through='assets.ITSBollardSystemAsset', + related_name="bollard_systems", + blank=True, + verbose_name=_("Équipements associés") + ) + + class Meta: + verbose_name = _("Système de bornes") + verbose_name_plural = _("Systèmes de bornes") + indexes = [ + models.Index(fields=["code"]), + ] + + def get_absolute_url(self): + return reverse('assets:its_assets_detail', kwargs={'asset_model': 'itsbollardsystem', 'asset_id': self.id}) + + @property + def plots_count(self): + return self.plots.count() + + +BOLLARD_TYPE_CHOICES = ( + ('automatic', _('Automatique / Escamotable')), + ('semi_automatic', _('Semi-automatique')), + ('fixed', _('Fixe')), + ('removable', _('Amovible')), +) + +class ITSBollardPlot(AbstractGeoAsset): + """ + Plot / borne physique individuel sur un système de bornes. + """ + geom = models.PointField(srid=3812, null=True, blank=True, verbose_name=_("Géométrie (Point)")) + system = models.ForeignKey(ITSBollardSystem, on_delete=models.CASCADE, related_name="plots", verbose_name=_("Système de bornes")) + model = models.ForeignKey(ITSAssetModel, on_delete=models.PROTECT, null=True, blank=True, verbose_name=_("Modèle de plot")) + position = models.PositiveSmallIntegerField(default=1, verbose_name=_("Position sur la traverse"), help_text=_("Ordre de gauche à droite (1, 2, 3...)")) + bollard_type = models.CharField(max_length=50, choices=BOLLARD_TYPE_CHOICES, default='automatic', verbose_name=_("Type de plot")) + location = models.ForeignKey(ITSLocation, related_name="bollard_plots", on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Localisation ITS")) + extra_data = models.JSONField(default=dict, blank=True, verbose_name=_("Métadonnées techniques")) + + def clean(self): + super().clean() + if not self.location_id and self.system and self.system.location_id: + self.location = self.system.location + + def save(self, *args, **kwargs): + if not self.location_id and self.system and self.system.location_id: + self.location = self.system.location + super().save(*args, **kwargs) + + class Meta: + verbose_name = _("Plot de borne") + verbose_name_plural = _("Plots de bornes") + ordering = ["system", "position"] + indexes = [ + models.Index(fields=["code"]), + ] + + def get_absolute_url(self): + return reverse('assets:its_assets_detail', kwargs={'asset_model': 'itsbollardplot', 'asset_id': self.id}) + + +class ITSBollardSystemAsset(models.Model): + """ + Table de liaison ManyToMany entre un ITSBollardSystem et des ITSGeoAsset + (caméras LAPI, totems, armoires électriques, feux bicolores, boucles...). + """ + class AssetRole(models.TextChoices): + CAMERA_ENTRY = "camera_entry", _("Caméra entrée (LAPI)") + CAMERA_EXIT = "camera_exit", _("Caméra sortie (LAPI)") + CAMERA_CCTV = "camera_cctv", _("Caméra de contexte / surveillance") + CABINET = "cabinet", _("Armoire électrique / automate") + TOTEM = "totem", _("Totem de commande") + TRAFFIC_LIGHT = "traffic_light", _("Feu de franchissement") + LOOP = "loop", _("Boucle de détection") + OTHER = "other", _("Autre") + + bollard_system = models.ForeignKey( + ITSBollardSystem, + on_delete=models.CASCADE, + related_name="system_assets", + verbose_name=_("Système de bornes") + ) + asset = models.ForeignKey( + ITSGeoAsset, + on_delete=models.CASCADE, + related_name="bollard_system_links", + verbose_name=_("Équipement ITS") + ) + role = models.CharField( + max_length=50, + choices=AssetRole.choices, + default=AssetRole.OTHER, + verbose_name=_("Rôle de l'équipement") + ) + comment = models.CharField(max_length=255, blank=True, null=True, verbose_name=_("Commentaire")) + created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Date d'association")) + + class Meta: + verbose_name = _("Équipement associé au système de bornes") + verbose_name_plural = _("Équipements associés au système de bornes") + unique_together = ('bollard_system', 'asset') + + def __str__(self): + return f"{self.bollard_system.code} - {self.asset.code} ({self.get_role_display()})" + + +class ITSBollardSystemContract(models.Model): + system = models.ForeignKey(ITSBollardSystem, on_delete=models.CASCADE, related_name="bollard_systems_contracts") + contract = models.ForeignKey("contracts.Contract", on_delete=models.CASCADE, related_name="bollard_systems_contracts") + status = models.CharField(max_length=50, choices=[('active', 'Actif'), ('inactive', 'Inactif'), ('archived', 'Archivé')], default='active') + maintenance_type = models.CharField(max_length=50, choices=[('equipments', 'Equipements'), ('office', 'Bureautique'), ('system', 'Système'), ('other', 'Autre')], default='other') + + def __str__(self): + return f"{self.system.code} - {self.contract.contract_number}" + + class Meta: + verbose_name = _("Contrat de système de bornes") + verbose_name_plural = _("Contrats de systèmes de bornes") + unique_together = ('system', 'contract') + diff --git a/loko/assets/templates/assets/its_index.html b/loko/assets/templates/assets/its_index.html index 64920fa..4013801 100644 --- a/loko/assets/templates/assets/its_index.html +++ b/loko/assets/templates/assets/its_index.html @@ -111,6 +111,11 @@ id: 'assets', type: 'circle', source: 'assets', + filter: [ + 'any', + ['==', ['geometry-type'], 'Point'], + ['==', ['geometry-type'], 'MultiPoint'] + ], paint: { 'circle-radius': [ 'interpolate', @@ -135,7 +140,32 @@ minzoom: 8 }; - const assets_layers_defs = [assets_layer_def]; + const assets_lines_layer_def = { + id: 'assets-lines', + type: 'line', + source: 'assets', + filter: [ + 'any', + ['==', ['geometry-type'], 'LineString'], + ['==', ['geometry-type'], 'MultiLineString'] + ], + paint: { + 'line-color': [ + 'case', + ['boolean', ['feature-state', 'highlighted'], false], '#ffeb3b', + '#0022ff' + ], + 'line-width': [ + 'case', + ['boolean', ['feature-state', 'highlighted'], false], 6, + 4 + ], + 'line-opacity': 0.85 + }, + minzoom: 8 + }; + + const assets_layers_defs = [assets_lines_layer_def, assets_layer_def]; // Filtrer les modèles par catégorie dans le formulaire des assets diff --git a/loko/assets/templates/its/detail_itsbollardplot.html b/loko/assets/templates/its/detail_itsbollardplot.html new file mode 100644 index 0000000..bf13498 --- /dev/null +++ b/loko/assets/templates/its/detail_itsbollardplot.html @@ -0,0 +1,92 @@ +{% extends "assets/detail_generic.html" %} +{% load i18n %} +{% load static %} + +{% block extra_blocks %} + +
{{ val }}| {% translate "Pos." %} | +{% translate "Code" %} | +{% translate "Type de borne" %} | +{% translate "Modèle" %} | +{% translate "N° de série" %} | +{% translate "Statut" %} | +{% translate "Action" %} | +
|---|---|---|---|---|---|---|
| + #{{ plot.position }} + | +
+
+ {{ plot.code }}
+
+ {% if plot.get_name %}
+ {{ plot.get_name }}
+ {% endif %}
+ |
+ + {% if plot.bollard_type == 'automatic' %} + {{ plot.get_bollard_type_display }} + {% elif plot.bollard_type == 'semi_automatic' %} + {{ plot.get_bollard_type_display }} + {% elif plot.bollard_type == 'removable' %} + {{ plot.get_bollard_type_display }} + {% else %} + {{ plot.get_bollard_type_display }} + {% endif %} + | +{{ plot.model.get_name|default:"-" }} | +{{ plot.serial_number|default:"-" }} |
+ + {% if plot.status == 'active' %} + {{ plot.get_status_display }} + {% elif plot.status == 'damaged' %} + {{ plot.get_status_display }} + {% elif plot.status == 'to_replace' %} + {{ plot.get_status_display }} + {% else %} + {{ plot.get_status_display }} + {% endif %} + | ++ + {% translate "Voir" %} + + | +
| {% translate "Rôle" %} | +{% translate "Équipement" %} | +{% translate "Modèle" %} | +{% translate "Statut" %} | +{% translate "Commentaire" %} | +{% translate "Action" %} | +
|---|---|---|---|---|---|
| + {% if 'camera' in item.role %} + {{ item.get_role_display }} + {% elif item.role == 'cabinet' %} + {{ item.get_role_display }} + {% elif item.role == 'totem' %} + {{ item.get_role_display }} + {% elif item.role == 'traffic_light' %} + {{ item.get_role_display }} + {% else %} + {{ item.get_role_display }} + {% endif %} + | +
+
+ {{ item.asset.code }}
+
+ {% if item.asset.get_name %}
+ {{ item.asset.get_name }}
+ {% endif %}
+ |
+ {{ item.asset.model.get_name|default:"-" }} | ++ {% if item.asset.status == 'active' %} + {{ item.asset.get_status_display }} + {% else %} + {{ item.asset.get_status_display }} + {% endif %} + | +{{ item.comment|default:"-" }} | ++ + + + | +
| {% translate "Numéro de contrat" %} | +{% translate "Type de maintenance" %} | +{% translate "Statut" %} | +
|---|---|---|
| {{ sc.contract.contract_number }} - {{ sc.contract.name }} | +{{ sc.get_maintenance_type_display }} | ++ {% if sc.status == 'active' %} + {% translate "Actif" %} + {% else %} + {{ sc.get_status_display }} + {% endif %} + | +
{{ val }}