fix(interventions): assign controller only when intervention status changes to finished
This commit is contained in:
parent
8df84704dc
commit
3920648000
3 changed files with 212 additions and 96 deletions
|
|
@ -658,21 +658,39 @@ class Intervention(models.Model):
|
||||||
if user_config and not user_config.is_intern:
|
if user_config and not user_config.is_intern:
|
||||||
self.created_by_provider = True
|
self.created_by_provider = True
|
||||||
|
|
||||||
# Attribution automatique de l'équipe de contrôle
|
# Attribution automatique de l'équipe de contrôle et du contrôleur
|
||||||
if (
|
if self.contract and getattr(self.contract, 'auto_assign_control_team', False):
|
||||||
self.contract and getattr(self.contract, 'auto_assign_control_team', False)
|
|
||||||
and not self.assigned_control_team_id
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
from interventions.services_control_assignment import resolve_control_team_and_member_for_intervention
|
from interventions.services_control_assignment import resolve_control_team_and_member_for_intervention
|
||||||
ctrl_team, ctrl_member = resolve_control_team_and_member_for_intervention(self)
|
|
||||||
if ctrl_team:
|
# 1. Équipe de contrôle : attribuée dès la création ou sauvegarde si non définie
|
||||||
self.assigned_control_team = ctrl_team
|
if not self.assigned_control_team_id:
|
||||||
self.assigned_controller = ctrl_member
|
ctrl_team, _ = resolve_control_team_and_member_for_intervention(self)
|
||||||
|
if ctrl_team:
|
||||||
|
self.assigned_control_team = ctrl_team
|
||||||
|
if 'update_fields' in kwargs and kwargs['update_fields'] is not None:
|
||||||
|
kwargs['update_fields'] = set(kwargs['update_fields']) | {'assigned_control_team'}
|
||||||
|
|
||||||
|
# 2. Contrôleur : attribué UNIQUEMENT lorsque l'intervention passe au statut 'terminé' ('finished')
|
||||||
|
if status_changed_to_finished and not self.assigned_controller_id:
|
||||||
|
if self.assigned_control_team:
|
||||||
|
_, ctrl_member = resolve_control_team_and_member_for_intervention(
|
||||||
|
self, target_team=self.assigned_control_team
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ctrl_team, ctrl_member = resolve_control_team_and_member_for_intervention(self)
|
||||||
|
if ctrl_team and not self.assigned_control_team_id:
|
||||||
|
self.assigned_control_team = ctrl_team
|
||||||
|
if 'update_fields' in kwargs and kwargs['update_fields'] is not None:
|
||||||
|
kwargs['update_fields'] = set(kwargs['update_fields']) | {'assigned_control_team'}
|
||||||
|
if ctrl_member:
|
||||||
|
self.assigned_controller = ctrl_member
|
||||||
|
if 'update_fields' in kwargs and kwargs['update_fields'] is not None:
|
||||||
|
kwargs['update_fields'] = set(kwargs['update_fields']) | {'assigned_controller'}
|
||||||
except Exception:
|
except Exception:
|
||||||
import logging
|
import logging
|
||||||
logging.getLogger(__name__).exception(
|
logging.getLogger(__name__).exception(
|
||||||
f"Error auto-assigning control team for intervention {self.code}"
|
f"Error auto-assigning control team/controller for intervention {self.code}"
|
||||||
)
|
)
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -12,95 +12,101 @@ def resolve_control_team_and_member(
|
||||||
thematic=None,
|
thematic=None,
|
||||||
maintain_type=None,
|
maintain_type=None,
|
||||||
locations=None,
|
locations=None,
|
||||||
|
target_team=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Détermine l'équipe de contrôle (CompanyTeam) et le contrôleur (CompanyMember) :
|
Détermine l'équipe de contrôle (CompanyTeam) et le contrôleur (CompanyMember) :
|
||||||
1. Équipes candidates gérant l'opération 'controls' (OPERATION_CONTROLS).
|
1. Si target_team est fourni, restreint la recherche des membres à cette équipe.
|
||||||
2. Filtrage thématique si l'équipe restreint ses thématiques.
|
Sinon :
|
||||||
3. Filtrage par type de maintenance (corrective, preventive, ameliorative) si l'équipe restreint ses types.
|
- Équipes candidates gérant l'opération 'controls' (OPERATION_CONTROLS).
|
||||||
4. Résolution spatiale si une géométrie est définie (cible et équipes) :
|
- Filtrage thématique si l'équipe restreint ses thématiques.
|
||||||
- team.geom.intersects(target_geom) ou team.geom.contains(point).
|
- Filtrage par type de maintenance (corrective, preventive, ameliorative) si l'équipe restreint ses types.
|
||||||
- Fallback sur toutes les équipes candidates si aucune géométrie n'est définie.
|
- Résolution spatiale si une géométrie est définie (cible et équipes) :
|
||||||
5. Calendrier d'assignation à la date scheduled_date :
|
- team.geom.intersects(target_geom) ou team.geom.contains(point).
|
||||||
|
- Fallback sur toutes les équipes candidates si aucune géométrie n'est définie.
|
||||||
|
2. Calendrier d'assignation à la date scheduled_date :
|
||||||
- Évalue le nombre d'agents planifiés et non absents.
|
- Évalue le nombre d'agents planifiés et non absents.
|
||||||
- Si aucun agent n'est disponible ou aucune date : retourne (None, None).
|
- Si aucun agent n'est disponible ou aucune date : retourne (target_team, None) ou (None, None).
|
||||||
- Sélectionne l'équipe ayant le plus d'agents disponibles.
|
- Sélectionne l'équipe ayant le plus d'agents disponibles.
|
||||||
- Sélectionne le premier membre disponible de cette équipe.
|
- Sélectionne le premier membre disponible de cette équipe.
|
||||||
"""
|
"""
|
||||||
from contracts.models import CompanyTeam, CompanyMember
|
from contracts.models import CompanyTeam, CompanyMember
|
||||||
from contracts.services_teams import get_teams_composition_for_period, get_member_absences_for_period
|
from contracts.services_teams import get_teams_composition_for_period, get_member_absences_for_period
|
||||||
|
|
||||||
teams_qs = CompanyTeam.objects.all().select_related('company').prefetch_related('thematics', 'maintain_types')
|
if target_team is not None:
|
||||||
candidate_teams = [t for t in teams_qs if t.handles_operation(CompanyTeam.OPERATION_CONTROLS)]
|
eligible_teams = [target_team]
|
||||||
|
|
||||||
if not candidate_teams:
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
# 1. Filtrage thématique si l'équipe restreint ses thématiques
|
|
||||||
if thematic:
|
|
||||||
candidate_teams = [
|
|
||||||
t for t in candidate_teams
|
|
||||||
if not t.thematics.all() or any(thm.id == thematic.id for thm in t.thematics.all())
|
|
||||||
]
|
|
||||||
|
|
||||||
if not candidate_teams:
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
# 2. Filtrage par type de maintenance si l'équipe restreint ses types de maintenance
|
|
||||||
if maintain_type:
|
|
||||||
candidate_teams = [
|
|
||||||
t for t in candidate_teams
|
|
||||||
if t.handles_maintain_type(maintain_type)
|
|
||||||
]
|
|
||||||
|
|
||||||
if not candidate_teams:
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
# 2. Résolution spatiale si géométrie définie
|
|
||||||
has_target_geom = bool(target_geom or point or locations)
|
|
||||||
teams_with_geom = [t for t in candidate_teams if t.geom is not None]
|
|
||||||
|
|
||||||
if has_target_geom and teams_with_geom:
|
|
||||||
spatial_matches = []
|
|
||||||
for team in teams_with_geom:
|
|
||||||
matched = False
|
|
||||||
if target_geom and team.geom:
|
|
||||||
try:
|
|
||||||
matched = team.geom.intersects(target_geom)
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Error checking team {team.id} geom intersects: {e}")
|
|
||||||
|
|
||||||
if not matched and point and team.geom:
|
|
||||||
try:
|
|
||||||
matched = team.geom.contains(point)
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Error checking team {team.id} geom contains point: {e}")
|
|
||||||
|
|
||||||
if not matched and locations and team.geom:
|
|
||||||
for loc in locations:
|
|
||||||
loc_geom = getattr(loc, 'geom', None)
|
|
||||||
if loc_geom:
|
|
||||||
try:
|
|
||||||
if team.geom.intersects(loc_geom):
|
|
||||||
matched = True
|
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if matched:
|
|
||||||
spatial_matches.append(team)
|
|
||||||
|
|
||||||
eligible_teams = spatial_matches
|
|
||||||
else:
|
else:
|
||||||
# Fallback sans géométrie : toutes les équipes candidates
|
teams_qs = CompanyTeam.objects.all().select_related('company').prefetch_related('thematics', 'maintain_types')
|
||||||
eligible_teams = candidate_teams
|
candidate_teams = [t for t in teams_qs if t.handles_operation(CompanyTeam.OPERATION_CONTROLS)]
|
||||||
|
|
||||||
if not eligible_teams:
|
if not candidate_teams:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
# 3. Calendrier d'assignation
|
# 1. Filtrage thématique si l'équipe restreint ses thématiques
|
||||||
|
if thematic:
|
||||||
|
candidate_teams = [
|
||||||
|
t for t in candidate_teams
|
||||||
|
if not t.thematics.all() or any(thm.id == thematic.id for thm in t.thematics.all())
|
||||||
|
]
|
||||||
|
|
||||||
|
if not candidate_teams:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# 2. Filtrage par type de maintenance si l'équipe restreint ses types de maintenance
|
||||||
|
if maintain_type:
|
||||||
|
candidate_teams = [
|
||||||
|
t for t in candidate_teams
|
||||||
|
if t.handles_maintain_type(maintain_type)
|
||||||
|
]
|
||||||
|
|
||||||
|
if not candidate_teams:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# 3. Résolution spatiale si géométrie définie
|
||||||
|
has_target_geom = bool(target_geom or point or locations)
|
||||||
|
teams_with_geom = [t for t in candidate_teams if t.geom is not None]
|
||||||
|
|
||||||
|
if has_target_geom and teams_with_geom:
|
||||||
|
spatial_matches = []
|
||||||
|
for team in teams_with_geom:
|
||||||
|
matched = False
|
||||||
|
if target_geom and team.geom:
|
||||||
|
try:
|
||||||
|
matched = team.geom.intersects(target_geom)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Error checking team {team.id} geom intersects: {e}")
|
||||||
|
|
||||||
|
if not matched and point and team.geom:
|
||||||
|
try:
|
||||||
|
matched = team.geom.contains(point)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Error checking team {team.id} geom contains point: {e}")
|
||||||
|
|
||||||
|
if not matched and locations and team.geom:
|
||||||
|
for loc in locations:
|
||||||
|
loc_geom = getattr(loc, 'geom', None)
|
||||||
|
if loc_geom:
|
||||||
|
try:
|
||||||
|
if team.geom.intersects(loc_geom):
|
||||||
|
matched = True
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if matched:
|
||||||
|
spatial_matches.append(team)
|
||||||
|
|
||||||
|
eligible_teams = spatial_matches
|
||||||
|
else:
|
||||||
|
# Fallback sans géométrie : toutes les équipes candidates
|
||||||
|
eligible_teams = candidate_teams
|
||||||
|
|
||||||
|
if not eligible_teams:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# 4. Calendrier d'assignation
|
||||||
if not scheduled_date:
|
if not scheduled_date:
|
||||||
return None, None
|
return (target_team if target_team else (eligible_teams[0] if eligible_teams else None)), None
|
||||||
|
|
||||||
if hasattr(scheduled_date, 'date'):
|
if hasattr(scheduled_date, 'date'):
|
||||||
target_d = scheduled_date.date()
|
target_d = scheduled_date.date()
|
||||||
|
|
@ -125,9 +131,9 @@ def resolve_control_team_and_member(
|
||||||
scored_teams.sort(key=lambda item: (-item[0], item[1].name))
|
scored_teams.sort(key=lambda item: (-item[0], item[1].name))
|
||||||
best_len, best_team, best_members = scored_teams[0]
|
best_len, best_team, best_members = scored_teams[0]
|
||||||
|
|
||||||
# Règle stricte : si aucun agent disponible ce jour-là, ne pas assigner
|
# Règle stricte pour le contrôleur : si aucun agent disponible ce jour-là, membre = None
|
||||||
if best_len == 0 or not best_members:
|
if best_len == 0 or not best_members:
|
||||||
return None, None
|
return (target_team, None) if target_team else (None, None)
|
||||||
|
|
||||||
best_member_id = best_members[0]['id']
|
best_member_id = best_members[0]['id']
|
||||||
best_member = CompanyMember.objects.filter(id=best_member_id).first()
|
best_member = CompanyMember.objects.filter(id=best_member_id).first()
|
||||||
|
|
@ -135,7 +141,7 @@ def resolve_control_team_and_member(
|
||||||
return best_team, best_member
|
return best_team, best_member
|
||||||
|
|
||||||
|
|
||||||
def resolve_control_team_and_member_for_intervention(intervention):
|
def resolve_control_team_and_member_for_intervention(intervention, target_date=None, target_team=None):
|
||||||
"""
|
"""
|
||||||
Extrait la géométrie, la localisation et la date de l'intervention
|
Extrait la géométrie, la localisation et la date de l'intervention
|
||||||
puis appelle resolve_control_team_and_member.
|
puis appelle resolve_control_team_and_member.
|
||||||
|
|
@ -149,12 +155,20 @@ def resolve_control_team_and_member_for_intervention(intervention):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Error transforming intervention coordinates to 3812: {e}")
|
logger.debug(f"Error transforming intervention coordinates to 3812: {e}")
|
||||||
|
|
||||||
target_date = (
|
if target_date is None:
|
||||||
intervention.planned_begin_time
|
if intervention.status == 'finished':
|
||||||
or intervention.begin_time
|
target_date = (
|
||||||
or intervention.creation_time
|
getattr(intervention, 'end_time', None)
|
||||||
or timezone.now().date()
|
or getattr(intervention, 'planned_end_time', None)
|
||||||
)
|
or timezone.now().date()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
target_date = (
|
||||||
|
intervention.planned_begin_time
|
||||||
|
or intervention.begin_time
|
||||||
|
or intervention.creation_time
|
||||||
|
or timezone.now().date()
|
||||||
|
)
|
||||||
|
|
||||||
locs = list(intervention.locations.all()) if (intervention.pk and hasattr(intervention, 'locations')) else []
|
locs = list(intervention.locations.all()) if (intervention.pk and hasattr(intervention, 'locations')) else []
|
||||||
|
|
||||||
|
|
@ -165,4 +179,5 @@ def resolve_control_team_and_member_for_intervention(intervention):
|
||||||
thematic=intervention.thematic,
|
thematic=intervention.thematic,
|
||||||
maintain_type=intervention.maintain_type,
|
maintain_type=intervention.maintain_type,
|
||||||
locations=locs,
|
locations=locs,
|
||||||
|
target_team=target_team,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,7 @@ class ControlAssignmentTests(TestCase):
|
||||||
self.contract_no_auto.thematics.add(self.thematic_structures)
|
self.contract_no_auto.thematics.add(self.thematic_structures)
|
||||||
|
|
||||||
def test_auto_assignment_structures_corrective(self):
|
def test_auto_assignment_structures_corrective(self):
|
||||||
"""Vérifie l'attribution automatique pour une intervention corrective Structures avec contrat activé."""
|
"""Vérifie l'attribution de l'équipe à la création et du contrôleur au statut terminé."""
|
||||||
# Coordonnées au centre de la zone (Bruxelles ~ 4.35, 50.85 -> en Lambert 72 ~ 150000, 170000)
|
|
||||||
p_poly = Polygon(((149000, 169000), (151000, 169000), (151000, 171000), (149000, 171000), (149000, 169000)))
|
p_poly = Polygon(((149000, 169000), (151000, 169000), (151000, 171000), (149000, 171000), (149000, 169000)))
|
||||||
geom = MultiPolygon([p_poly], srid=3812)
|
geom = MultiPolygon([p_poly], srid=3812)
|
||||||
|
|
||||||
|
|
@ -117,6 +116,30 @@ class ControlAssignmentTests(TestCase):
|
||||||
planned_begin_time=timezone.now(),
|
planned_begin_time=timezone.now(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# À la création (statut à traiter), seule l'équipe de contrôle est assignée
|
||||||
|
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
||||||
|
self.assertIsNone(itv.assigned_controller)
|
||||||
|
|
||||||
|
# Lors du passage au statut 'terminé', le contrôleur disponible est automatiquement assigné
|
||||||
|
itv.status = 'finished'
|
||||||
|
itv.save()
|
||||||
|
itv.refresh_from_db()
|
||||||
|
self.assertEqual(itv.assigned_controller, self.member_ctrl)
|
||||||
|
|
||||||
|
def test_auto_assignment_when_created_directly_finished(self):
|
||||||
|
"""Vérifie l'attribution directe de l'équipe et du contrôleur si créée au statut terminé."""
|
||||||
|
p_poly = Polygon(((149000, 169000), (151000, 169000), (151000, 171000), (149000, 171000), (149000, 169000)))
|
||||||
|
geom = MultiPolygon([p_poly], srid=3812)
|
||||||
|
|
||||||
|
itv = Intervention.objects.create(
|
||||||
|
maintain_type='corrective',
|
||||||
|
thematic=self.thematic_structures,
|
||||||
|
contract=self.contract_auto,
|
||||||
|
geom=geom,
|
||||||
|
status='finished',
|
||||||
|
planned_begin_time=timezone.now(),
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
||||||
self.assertEqual(itv.assigned_controller, self.member_ctrl)
|
self.assertEqual(itv.assigned_controller, self.member_ctrl)
|
||||||
|
|
||||||
|
|
@ -126,6 +149,7 @@ class ControlAssignmentTests(TestCase):
|
||||||
maintain_type='preventive',
|
maintain_type='preventive',
|
||||||
thematic=self.thematic_structures,
|
thematic=self.thematic_structures,
|
||||||
contract=self.contract_auto,
|
contract=self.contract_auto,
|
||||||
|
status='finished',
|
||||||
planned_begin_time=timezone.now(),
|
planned_begin_time=timezone.now(),
|
||||||
)
|
)
|
||||||
self.assertIsNone(itv.assigned_control_team)
|
self.assertIsNone(itv.assigned_control_team)
|
||||||
|
|
@ -138,6 +162,7 @@ class ControlAssignmentTests(TestCase):
|
||||||
maintain_type='preventive',
|
maintain_type='preventive',
|
||||||
thematic=self.thematic_structures,
|
thematic=self.thematic_structures,
|
||||||
contract=self.contract_auto,
|
contract=self.contract_auto,
|
||||||
|
status='finished',
|
||||||
planned_begin_time=timezone.now(),
|
planned_begin_time=timezone.now(),
|
||||||
)
|
)
|
||||||
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
||||||
|
|
@ -150,6 +175,7 @@ class ControlAssignmentTests(TestCase):
|
||||||
maintain_type='ameliorative',
|
maintain_type='ameliorative',
|
||||||
thematic=self.thematic_structures,
|
thematic=self.thematic_structures,
|
||||||
contract=self.contract_auto,
|
contract=self.contract_auto,
|
||||||
|
status='finished',
|
||||||
planned_begin_time=timezone.now(),
|
planned_begin_time=timezone.now(),
|
||||||
)
|
)
|
||||||
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
||||||
|
|
@ -161,6 +187,7 @@ class ControlAssignmentTests(TestCase):
|
||||||
maintain_type='corrective',
|
maintain_type='corrective',
|
||||||
thematic=self.thematic_roads,
|
thematic=self.thematic_roads,
|
||||||
contract=self.contract_auto,
|
contract=self.contract_auto,
|
||||||
|
status='finished',
|
||||||
planned_begin_time=timezone.now(),
|
planned_begin_time=timezone.now(),
|
||||||
)
|
)
|
||||||
self.assertIsNone(itv.assigned_control_team)
|
self.assertIsNone(itv.assigned_control_team)
|
||||||
|
|
@ -173,6 +200,7 @@ class ControlAssignmentTests(TestCase):
|
||||||
maintain_type='corrective',
|
maintain_type='corrective',
|
||||||
thematic=self.thematic_roads,
|
thematic=self.thematic_roads,
|
||||||
contract=self.contract_auto,
|
contract=self.contract_auto,
|
||||||
|
status='finished',
|
||||||
planned_begin_time=timezone.now(),
|
planned_begin_time=timezone.now(),
|
||||||
)
|
)
|
||||||
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
||||||
|
|
@ -184,6 +212,7 @@ class ControlAssignmentTests(TestCase):
|
||||||
maintain_type='corrective',
|
maintain_type='corrective',
|
||||||
thematic=self.thematic_structures,
|
thematic=self.thematic_structures,
|
||||||
contract=self.contract_no_auto,
|
contract=self.contract_no_auto,
|
||||||
|
status='finished',
|
||||||
planned_begin_time=timezone.now(),
|
planned_begin_time=timezone.now(),
|
||||||
)
|
)
|
||||||
self.assertIsNone(itv.assigned_control_team)
|
self.assertIsNone(itv.assigned_control_team)
|
||||||
|
|
@ -196,6 +225,7 @@ class ControlAssignmentTests(TestCase):
|
||||||
thematic=self.thematic_structures,
|
thematic=self.thematic_structures,
|
||||||
contract=self.contract_auto,
|
contract=self.contract_auto,
|
||||||
geom=None,
|
geom=None,
|
||||||
|
status='finished',
|
||||||
planned_begin_time=timezone.now(),
|
planned_begin_time=timezone.now(),
|
||||||
)
|
)
|
||||||
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
||||||
|
|
@ -255,3 +285,56 @@ class ControlAssignmentTests(TestCase):
|
||||||
itv.status = 'in_progress'
|
itv.status = 'in_progress'
|
||||||
itv.save(update_fields=['status'])
|
itv.save(update_fields=['status'])
|
||||||
self.assertFalse(can_control_intervention(self.user_ctrl, itv))
|
self.assertFalse(can_control_intervention(self.user_ctrl, itv))
|
||||||
|
|
||||||
|
def test_controller_not_assigned_during_in_progress_or_on_pause(self):
|
||||||
|
"""Le contrôleur ne doit pas être assigné aux statuts intermédiaires (to_be_processed, in_progress, on_pause)."""
|
||||||
|
itv = Intervention.objects.create(
|
||||||
|
maintain_type='corrective',
|
||||||
|
thematic=self.thematic_structures,
|
||||||
|
contract=self.contract_auto,
|
||||||
|
status='to_be_processed',
|
||||||
|
planned_begin_time=timezone.now(),
|
||||||
|
)
|
||||||
|
self.assertEqual(itv.assigned_control_team, self.team_ctrl)
|
||||||
|
self.assertIsNone(itv.assigned_controller)
|
||||||
|
|
||||||
|
# Passage à in_progress
|
||||||
|
itv.status = 'in_progress'
|
||||||
|
itv.save()
|
||||||
|
itv.refresh_from_db()
|
||||||
|
self.assertIsNone(itv.assigned_controller)
|
||||||
|
|
||||||
|
# Passage à on_pause
|
||||||
|
itv.status = 'on_pause'
|
||||||
|
itv.pause_reason = 'weather'
|
||||||
|
itv.save()
|
||||||
|
itv.refresh_from_db()
|
||||||
|
self.assertIsNone(itv.assigned_controller)
|
||||||
|
|
||||||
|
# Passage à finished -> contrôleur assigné
|
||||||
|
itv.status = 'finished'
|
||||||
|
itv.save()
|
||||||
|
itv.refresh_from_db()
|
||||||
|
self.assertEqual(itv.assigned_controller, self.member_ctrl)
|
||||||
|
|
||||||
|
def test_controller_manual_not_overwritten_on_finished(self):
|
||||||
|
"""Un contrôleur assigné manuellement ne doit pas être écrasé lors du passage à terminé."""
|
||||||
|
user_other = self.User.objects.create_user(username='other_ctrl', password='pwd')
|
||||||
|
other_ctrl = CompanyMember.objects.create(
|
||||||
|
name='Autre Contrôleur',
|
||||||
|
user=user_other
|
||||||
|
)
|
||||||
|
itv = Intervention.objects.create(
|
||||||
|
maintain_type='corrective',
|
||||||
|
thematic=self.thematic_structures,
|
||||||
|
contract=self.contract_auto,
|
||||||
|
status='in_progress',
|
||||||
|
assigned_control_team=self.team_ctrl,
|
||||||
|
assigned_controller=other_ctrl,
|
||||||
|
planned_begin_time=timezone.now(),
|
||||||
|
)
|
||||||
|
itv.status = 'finished'
|
||||||
|
itv.save()
|
||||||
|
itv.refresh_from_db()
|
||||||
|
self.assertEqual(itv.assigned_controller, other_ctrl)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue