loko/streetup/assets/models/inspection.py

213 lines
7.6 KiB
Python

from django.db import models
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
INSPECTION_RESULT_CHOICES = (
('compliant', _('Conforme')),
('non_compliant', _('Non conforme / Anomalie')),
('updated', _('Données corrigées')),
('damaged', _('Endommagé')),
('missing', _('Absent / Retiré')),
)
INSPECTION_NATURE_CHOICES = (
('visual', _('Inspection visuelle')),
('technical', _('Inspection technique')),
('post_intervention', _('Suivi / Qualité post-intervention')),
)
INSPECTOR_ROLE_CHOICES = (
('observer', _('Observer / Agent de terrain')),
('provider', _('Prestataire technique externe')),
('manager', _('Gestionnaire / Contrôleur interne')),
)
class AssetInspection(models.Model):
"""
Modèle d'enregistrement générique d'une vérification/inspection sur le terrain d'une cible (Asset, Localisation, Intervention).
"""
asset_content_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
verbose_name=_("Type d'asset / Cible"),
limit_choices_to={'app_label__in': ['assets', 'interventions']}
)
asset_object_id = models.PositiveIntegerField(verbose_name=_("ID de la cible"))
asset = GenericForeignKey('asset_content_type', 'asset_object_id')
nature = models.CharField(
max_length=50,
choices=INSPECTION_NATURE_CHOICES,
default='visual',
verbose_name=_("Nature de l'inspection")
)
inspector_role = models.CharField(
max_length=50,
choices=INSPECTOR_ROLE_CHOICES,
default='observer',
verbose_name=_("Rôle de l'inspecteur")
)
inspector = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='asset_inspections',
verbose_name=_("Inspecteur / Vérificateur")
)
inspection_date = models.DateTimeField(default=timezone.now, verbose_name=_("Date d'inspection"))
result_status = models.CharField(
max_length=50,
choices=INSPECTION_RESULT_CHOICES,
default='compliant',
verbose_name=_("Résultat du contrôle")
)
verified_obsolescence = models.CharField(
max_length=50,
null=True,
blank=True,
verbose_name=_("État d'obsolescence constaté")
)
notes = models.TextField(null=True, blank=True, verbose_name=_("Remarques / Constats"))
apply_changes_to_asset = models.BooleanField(
default=True,
verbose_name=_("Appliquer directement les modifications à l'asset")
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = _("Inspection d'asset (Générique)")
verbose_name_plural = _("Inspections d'assets (Génériques)")
ordering = ['-inspection_date']
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.apply_changes_to_asset and self.asset:
target_asset = self.asset
modified = False
if hasattr(target_asset, 'last_inspection_date'):
target_asset.last_inspection_date = self.inspection_date
modified = True
if hasattr(target_asset, 'inspection_status'):
target_asset.inspection_status = self.result_status
modified = True
if hasattr(target_asset, 'validation_status'):
target_asset.validation_status = 'validated'
modified = True
if hasattr(target_asset, 'is_compliant'):
if self.result_status in ('non_compliant', 'damaged', 'missing'):
target_asset.is_compliant = False
if hasattr(target_asset, 'non_compliance_reason') and self.notes:
target_asset.non_compliance_reason = self.notes
else:
target_asset.is_compliant = True
if hasattr(target_asset, 'non_compliance_reason'):
target_asset.non_compliance_reason = None
modified = True
if self.verified_obsolescence and hasattr(target_asset, 'obsolescence'):
target_asset.obsolescence = self.verified_obsolescence
modified = True
if modified:
target_asset.save()
@property
def status_badge_class(self):
mapping = {
'compliant': 'bg-success',
'non_compliant': 'bg-warning text-dark',
'damaged': 'bg-danger',
'missing': 'bg-dark',
'updated': 'bg-info text-dark',
}
return mapping.get(self.result_status, 'bg-secondary')
@property
def target_display(self):
if self.asset:
return str(self.asset)
return f"Cible #{self.asset_object_id}"
def __str__(self):
asset_str = str(self.asset) if self.asset else f"Asset #{self.asset_object_id}"
date_str = self.inspection_date.strftime('%d/%m/%Y') if self.inspection_date else ''
return f"Inspection {asset_str} ({date_str}) - {self.get_result_status_display()}"
class ParkingSpotInspection(AssetInspection):
"""
Spécialisation de l'inspection pour les emplacements de stationnement (vélos, trottinettes, livraison, PMR).
"""
verified_standard_stand_count = models.IntegerField(
null=True,
blank=True,
verbose_name=_("Nombre d'arceaux classiques constaté")
)
verified_cargo_stand_count = models.IntegerField(
null=True,
blank=True,
verbose_name=_("Nombre d'arceaux cargo constaté")
)
verified_stand_type = models.CharField(
max_length=50,
null=True,
blank=True,
verbose_name=_("Type d'arceau constaté")
)
verified_has_scooter_drop_zone = models.BooleanField(
null=True,
blank=True,
verbose_name=_("Drop zone trottinette constatée")
)
verified_marking_quality = models.CharField(
max_length=50,
null=True,
blank=True,
verbose_name=_("Qualité du marquage constatée")
)
class Meta:
verbose_name = _("Inspection d'emplacement de stationnement")
verbose_name_plural = _("Inspections d'emplacements de stationnement")
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.apply_changes_to_asset and self.asset and self.asset.__class__.__name__ == 'ParkingSpot':
target_asset = self.asset
modified = False
if self.verified_standard_stand_count is not None:
target_asset.standard_stand_count = self.verified_standard_stand_count
modified = True
if self.verified_cargo_stand_count is not None:
target_asset.cargo_stand_count = self.verified_cargo_stand_count
modified = True
if self.verified_stand_type is not None:
target_asset.stand_type = self.verified_stand_type
modified = True
if self.verified_has_scooter_drop_zone is not None:
target_asset.has_scooter_drop_zone = self.verified_has_scooter_drop_zone
modified = True
if self.verified_marking_quality is not None:
target_asset.marking_quality = self.verified_marking_quality
modified = True
if modified:
target_asset.save()