# ----------------------------- # Engineering Structures Models # ----------------------------- 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.core.exceptions import ValidationError import os from assets.models import AbstractLocation, AbstractAsset, AbstractGeoAsset, AbstractAssetModel from common.models import Municipality, Document from common.storages import PrivateMediaStorage private_storage = PrivateMediaStorage() STRUCTURE_TYPE_CHOICES = ( ('tunnel', 'Tunnnel'), ('bridge', 'Pont'), ('viaduct', 'Viaduc'), ('overpass', 'Passerelle'), ('underpass', 'Passage souterrain'), ('gallery', 'Galerie technique'), ('atypical', 'Ouvrage atypique'), ) # Structure locations class StructureLocation(AbstractLocation): geom = models.MultiPolygonField(srid=3812, null=True,blank=True) parent_location = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name="child_locations") is_main_location = models.BooleanField(default=False, verbose_name="Main Location") structure = models.ForeignKey('Structure', on_delete=models.SET_NULL, null=True, blank=True, related_name="all_locations") def get_active_assets(self): def collect_assets(location): assets = list(location.assets.filter(status='active')) for child in location.child_locations.all(): assets += collect_assets(child) return assets return collect_assets(self) def get_all_assets(self): def collect_assets(location): assets = list(location.assets.all()) for child in location.child_locations.all(): assets += collect_assets(child) return assets return collect_assets(self) def get_direct_child_assets(self): return self.assets.all() def clean(self): if self.is_main_location and StructureLocation.objects.filter(structure=self.structure, is_main_location=True).exclude(pk=self.pk).exists(): raise ValidationError("Cette structure a déjà une localisation principale.") def get_absolute_url(self): return reverse( 'assets:structures_locations_detail', kwargs={ # 'asset_model': content_type.model, 'location_id': self.id } ) class Meta: verbose_name = "Structure Location" verbose_name_plural = "Structure Locations" ordering = ["code"] class Structure(models.Model): code = models.CharField(max_length=50, unique=True) name_fr = models.CharField(max_length=200,null=True,blank=True) name_nl = models.CharField(max_length=200,null=True,blank=True) structure_type = models.CharField(max_length=200, choices=STRUCTURE_TYPE_CHOICES, null=True, blank=True) main_location = models.OneToOneField(StructureLocation, on_delete=models.SET_NULL, null=True, blank=True, related_name="main_structure") description = models.TextField(null=True, blank=True) linked_folder = models.CharField(max_length=1024, null=True, blank=True) structure_number = models.IntegerField(null=True, blank=True, verbose_name="Numéro de l'ouvrage") def get_name(self): lang = get_language() name = getattr(self, f"name_{lang}", self.name_fr) or "" # Fallback to name_fr return name def get_active_assets(self): return self.assets.filter(status='active') def get_all_assets(self): return self.assets.all() def get_all_locations(self): return self.all_locations.all() def get_main_location(self): return self.main_location def __str__(self): return self.code + " - " + self.get_name() class Meta: ordering = ["code"] def upload_to_structure(instance, filename): location = instance.structure code = slugify(location.code or "unknown") # Fallback si code est None return os.path.join("assets_documents", "structures", code, filename) TU_DOCUMENT_TYPE_CHOICES = ( ('as_built', 'As Built'), ('cme', "CME"), ('inspection_ce', 'Inspection G.C.'), ('inspection_em', 'Inspection E.M.'), ('pis', 'PIS'), ('other', 'Autre'), ) # Structure Location Document class StructureDocument(Document): structure = models.ForeignKey(Structure, on_delete=models.CASCADE, related_name="documents", verbose_name="Associated Structure") file = models.FileField(upload_to=upload_to_structure, storage=private_storage, verbose_name="Fichier", max_length=255) thumbnail = models.ImageField(upload_to=upload_to_structure, storage=private_storage, null=True, blank=True, max_length=255) document_type = models.CharField(max_length=50, choices=TU_DOCUMENT_TYPE_CHOICES, default='other', verbose_name="Type de document") SC_MAINTENANCE_TYPE_CHOICES = ( ('electromeca', 'Eléctromécanique'), ('civil_engineering', 'Génie civil'), ('urgent_cleaning', 'Nettoyage urgent'), ('other', 'Autre'), ) class StructureContract(models.Model): structure = models.ForeignKey(Structure, on_delete=models.CASCADE, related_name="contracts") contract = models.ForeignKey("contracts.Contract", on_delete=models.CASCADE, related_name="structure_contracts") status = models.CharField(max_length=50, choices=[('active', 'Actif'), ('inactive', 'Inactif'), ('archived', 'Archivé')], default='active') maintenance_type = models.CharField(max_length=50, choices=SC_MAINTENANCE_TYPE_CHOICES, default='other') def __str__(self): return f"{self.structure.code} - {self.contract.contract_number}" class Meta: verbose_name = "Structure Contract" verbose_name_plural = "Structure Contracts" unique_together = ('structure', 'contract') class StructureAssetModel(AbstractAssetModel): class Meta: verbose_name = "Structure Asset Model" verbose_name_plural = "Structure Asset Models" class StructureGeoAsset(AbstractGeoAsset): model = models.ForeignKey(StructureAssetModel, on_delete=models.PROTECT) location = models.ForeignKey(StructureLocation, related_name="assets", on_delete=models.SET_NULL, null=True, blank=True) structure = models.ForeignKey(Structure, on_delete=models.SET_NULL, related_name="assets", null=True, blank=True) parent_asset = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name="child_assets") class Meta: indexes = [ models.Index(fields=["code"]), ] def get_absolute_url(self): return reverse('assets:structures_assets_detail', kwargs={'asset_model': 'structuregeoasset', 'asset_id':self.id})