208 lines
8 KiB
Python
208 lines
8 KiB
Python
from django.contrib.gis.db import models
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.utils.translation import get_language, gettext_lazy as _
|
|
|
|
from common.models import Thematic
|
|
|
|
# Abstract base model for locations
|
|
class AbstractLocation(models.Model):
|
|
code = models.CharField(max_length=50)
|
|
name_fr = models.CharField(max_length=200,null=True,blank=True)
|
|
name_nl = models.CharField(max_length=200,null=True,blank=True)
|
|
lon = models.FloatField(null=True,blank=True)
|
|
lat = models.FloatField(null=True,blank=True)
|
|
geom = models.PolygonField(srid=3812, null=True,blank=True)
|
|
geojson = models.TextField(null=True, blank=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
updated_by = models.ForeignKey('auth.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='%(app_label)s_%(class)s_location_updated_by', verbose_name="Mis à jour par")
|
|
|
|
def get_name(self):
|
|
lang = get_language()
|
|
name = getattr(self, f"name_{lang}", self.name_fr) or "" # Fallback to name_fr
|
|
return name
|
|
|
|
def __str__(self):
|
|
return self.code + " - " + self.get_name()
|
|
|
|
class Meta:
|
|
abstract = True
|
|
ordering = ["code"]
|
|
|
|
|
|
|
|
# Category of assets
|
|
class AssetCategory(models.Model):
|
|
thematic = models.ForeignKey(Thematic, on_delete=models.SET_NULL, null=True, blank=True, related_name="asset_categories")
|
|
parent = models.ForeignKey(
|
|
'self',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='children',
|
|
verbose_name=_("Catégorie parente"),
|
|
help_text=_("Laisser vide pour une catégorie de premier niveau. Une sous-catégorie ne peut pas avoir d'enfants."),
|
|
)
|
|
code = models.CharField(max_length=200)
|
|
name_fr = models.CharField(max_length=255)
|
|
name_nl = models.CharField(max_length=255)
|
|
allowed_models = models.ManyToManyField(
|
|
ContentType,
|
|
blank=True,
|
|
related_name="asset_categories",
|
|
verbose_name=_("Modèles Django autorisés"),
|
|
help_text=_("Les types de modèles d'assets (Django) autorisés pour cette catégorie.")
|
|
)
|
|
|
|
def get_name(self):
|
|
lang = get_language()
|
|
return getattr(self, f"name_{lang}", self.name_fr) # Fallback to name_fr
|
|
|
|
def __str__(self):
|
|
return f"{self.get_name()}"
|
|
|
|
@property
|
|
def is_leaf(self):
|
|
"""Retourne True si cette catégorie n'a pas d'enfants (feuille de l'arbre)."""
|
|
return not self.children.exists()
|
|
|
|
def get_children(self):
|
|
"""Retourne le queryset des sous-catégories directes."""
|
|
return self.children.all()
|
|
|
|
def get_category_ids_with_children(self):
|
|
"""Retourne la liste des ids : cette catégorie + toutes ses sous-catégories."""
|
|
ids = [self.pk]
|
|
ids.extend(self.children.values_list('pk', flat=True))
|
|
return ids
|
|
|
|
class Meta:
|
|
ordering = ["thematic", "name_fr"]
|
|
|
|
|
|
# Abstract base model for "models of assets"
|
|
class AbstractAssetModel(models.Model):
|
|
code = models.CharField(max_length=50)
|
|
name_fr = models.CharField(max_length=200,null=True,blank=True)
|
|
name_nl = models.CharField(max_length=200,null=True,blank=True)
|
|
description = models.TextField(null=True,blank=True)
|
|
brand = models.CharField(max_length=500, null=True,blank=True)
|
|
category = models.ForeignKey(AssetCategory, on_delete=models.SET_NULL, null=True, blank=True)
|
|
|
|
def get_name(self):
|
|
lang = get_language()
|
|
name = getattr(self, f"name_{lang}", self.name_fr) or "" # Fallback to name_fr
|
|
return name
|
|
|
|
def __str__(self):
|
|
return self.code + " - " + self.get_name()
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
|
|
ASSET_STATUS_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é'),
|
|
}
|
|
|
|
ASSET_OBSOLESCENCE_CHOICES = {
|
|
'good': _('Bon état'),
|
|
'medium': _('Moyen'),
|
|
'bad': _('Dégradé'),
|
|
'critical': _('Critique'),
|
|
'unknown': _('Inconnu'),
|
|
}
|
|
|
|
ASSET_VALIDATION_STATUS_CHOICES = {
|
|
'to_be_validated': _('À valider'),
|
|
'validated': _('Validé'),
|
|
'to_be_completed': _('À compléter'),
|
|
'rejected': _('Rejeté'),
|
|
}
|
|
|
|
# Abstract base model for assets
|
|
class AbstractAsset(models.Model):
|
|
code = models.CharField(max_length=50)
|
|
name_fr = models.CharField(max_length=200,null=True,blank=True)
|
|
name_nl = models.CharField(max_length=200,null=True,blank=True)
|
|
status = models.CharField(choices=ASSET_STATUS_CHOICES, default='active', max_length=50)
|
|
obsolescence = models.CharField(choices=ASSET_OBSOLESCENCE_CHOICES, default='unknown', null=True, blank=True)
|
|
installation_date = models.DateTimeField(null=True,blank=True)
|
|
uninstallation_date = models.DateTimeField(null=True,blank=True)
|
|
serial_number = models.CharField(max_length=200,null=True,blank=True)
|
|
warranty_duration = models.IntegerField(null=True,blank=True, verbose_name="Duration of the warranty (months)")
|
|
last_inspection_date = models.DateTimeField(null=True,blank=True)
|
|
last_preventive_date = models.DateTimeField(null=True,blank=True)
|
|
last_corrective_date = models.DateTimeField(null=True,blank=True)
|
|
last_survey_date = models.DateTimeField(null=True,blank=True)
|
|
category = models.ForeignKey(AssetCategory, on_delete=models.SET_NULL, null=True, blank=True)
|
|
brand = models.CharField(max_length=500, null=True,blank=True)
|
|
replaced_by = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='replaces', verbose_name="Remplacé par")
|
|
marked_as_removed_date = models.DateTimeField(null=True, blank=True, verbose_name="Date de marquage comme retiré")
|
|
marked_as_removed_by = models.ForeignKey('auth.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='%(app_label)s_%(class)s_marked_as_removed', verbose_name="Marqué comme retiré par")
|
|
note = models.TextField(null=True, blank=True, verbose_name="Note")
|
|
validation_status = models.CharField(max_length=50, choices=ASSET_VALIDATION_STATUS_CHOICES, null=True, blank=True, verbose_name="Statut de validation")
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
updated_by = models.ForeignKey('auth.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='%(app_label)s_%(class)s_updated_by', verbose_name="Mis à jour par")
|
|
|
|
def get_replacement_chain(self):
|
|
"""Get the complete replacement chain starting from this asset."""
|
|
chain = [self]
|
|
current = self
|
|
seen_ids = {self.id}
|
|
|
|
while current.replaced_by:
|
|
if current.replaced_by.id in seen_ids: # Avoid infinite loops
|
|
break
|
|
chain.append(current.replaced_by)
|
|
seen_ids.add(current.replaced_by.id)
|
|
current = current.replaced_by
|
|
|
|
return chain
|
|
|
|
def get_original_asset(self):
|
|
"""Get the first asset in the replacement chain."""
|
|
original = self
|
|
seen_ids = {self.id}
|
|
|
|
# Try to find an asset that replaces this one
|
|
replacer = self.replaces.first()
|
|
while replacer:
|
|
if replacer.id in seen_ids: # Avoid infinite loops
|
|
break
|
|
original = replacer
|
|
seen_ids.add(replacer.id)
|
|
replacer = replacer.replaces.first()
|
|
|
|
return original
|
|
|
|
def get_name(self):
|
|
lang = get_language()
|
|
name = getattr(self, f"name_{lang}", self.name_fr) or "" # Fallback to name_fr
|
|
return name
|
|
|
|
def __str__(self):
|
|
return self.code + " - " + self.get_name()
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
|
|
# Abstract base model for geo assets
|
|
class AbstractGeoAsset(AbstractAsset):
|
|
lon = models.FloatField(null=True,blank=True)
|
|
lat = models.FloatField(null=True,blank=True)
|
|
geom = models.PointField(srid=3812, null=True,blank=True)
|
|
geojson = models.TextField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
|
|
|