loko/streetup/assets/models/core.py
2026-07-22 14:48:40 +02:00

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