feat: track status changes in InterventionTimeLine when generating contract documents

This commit is contained in:
kdeterme 2026-08-28 16:05:52 +02:00
parent ce79d913ea
commit 1df0c1b8c1
2 changed files with 133 additions and 3 deletions

View file

@ -232,3 +232,111 @@ class ContractDisplayNameTests(TestCase):
def test_display_name_long_description_truncated(self): def test_display_name_long_description_truncated(self):
self.assertTrue(self.contract_long_desc.display_name.startswith("CTR-003 (Travaux et maintenance préventive et correct")) self.assertTrue(self.contract_long_desc.display_name.startswith("CTR-003 (Travaux et maintenance préventive et correct"))
self.assertTrue(self.contract_long_desc.display_name.endswith("…)")) self.assertTrue(self.contract_long_desc.display_name.endswith("…)"))
class GenerateClaimDeclarationTimelineTests(TestCase):
def setUp(self):
from decimal import Decimal
from datetime import date
from contracts.models import Contract, ContractOrder, ContractPost
from common.models import UserContractAccess
from interventions.models import Intervention, InterventionTimeLine, InterventionContractPost, InterventionPostPeriodQuantity
self.client = Client()
self.user = User.objects.create_user(username="admin_user", password="password")
self.user_config = UserConfig.objects.create(user=self.user, is_intern=True)
self.admin_role, _ = Role.objects.get_or_create(name="admin")
self.user_config.roles.add(self.admin_role)
self.client.login(username="admin_user", password="password")
self.company = Company.objects.create(name="Company A")
self.contract = Contract.objects.create(
company=self.company,
contract_number="CTR-TEST-001",
start_date=date(2025, 1, 1),
end_date=date(2026, 1, 1),
)
UserContractAccess.objects.create(
user_config=self.user_config,
contract=self.contract,
can_view_interventions=True,
)
self.order = ContractOrder.objects.create(
contract=self.contract,
order_code="ORD-TEST-01",
order_status="pending",
order_date=date(2025, 1, 1),
delivery_date=date(2025, 1, 15),
description="Commande test",
)
self.post = ContractPost.objects.create(
contract=self.contract,
order_number=1,
code="P1",
description_fr="Poste 1",
description_nl="Post 1",
unit="u",
unit_price=Decimal("10.00"),
)
self.intervention_validated = Intervention.objects.create(
title="Intervention Validated",
contract=self.contract,
order=self.order,
status="validated",
)
InterventionContractPost.objects.create(
intervention=self.intervention_validated,
contract_post=self.post,
quantity=Decimal("2.00"),
control_status="validated",
)
self.intervention_period = Intervention.objects.create(
title="Intervention with Validated Period",
contract=self.contract,
order=self.order,
status="to_be_processed",
)
InterventionPostPeriodQuantity.objects.create(
intervention=self.intervention_period,
contract_post=self.post,
period_number=1,
quantity=Decimal("3.00"),
validated_at=timezone.now(),
validated_by=self.user,
status="validated",
)
def test_generate_documents_creates_timeline_events(self):
from interventions.models import InterventionTimeLine
url = reverse("contracts:generate_documents_for_order", args=[self.order.id])
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
# 1. Check validated intervention status & timeline
self.intervention_validated.refresh_from_db()
self.assertEqual(self.intervention_validated.status, "invoiced")
timeline_event_1 = InterventionTimeLine.objects.filter(
intervention=self.intervention_validated,
to_status="invoiced",
).first()
self.assertIsNotNone(timeline_event_1)
self.assertEqual(timeline_event_1.from_status, "validated")
self.assertEqual(timeline_event_1.event_type, "status_change")
self.assertEqual(timeline_event_1.event_user, self.user)
# 2. Check intervention with period status & timeline
self.intervention_period.refresh_from_db()
self.assertEqual(self.intervention_period.status, "invoiced")
timeline_event_2 = InterventionTimeLine.objects.filter(
intervention=self.intervention_period,
to_status="invoiced",
).first()
self.assertIsNotNone(timeline_event_2)
self.assertEqual(timeline_event_2.from_status, "to_be_processed")
self.assertEqual(timeline_event_2.event_type, "status_change")
self.assertEqual(timeline_event_2.event_user, self.user)

View file

@ -1372,7 +1372,7 @@ def generate_documents_for_order(request, order_id):
messages.error(request, _("Vous n'avez pas la permission de générer des documents pour cette commande.")) messages.error(request, _("Vous n'avez pas la permission de générer des documents pour cette commande."))
return redirect("contracts:contracts_orders_detail", order_id=order.id) return redirect("contracts:contracts_orders_detail", order_id=order.id)
from interventions.models import InterventionContractPost, InterventionPostPeriodQuantity from interventions.models import InterventionContractPost, InterventionPostPeriodQuantity, InterventionTimeLine
# Collecter les commandes directes validées AVANT génération des documents (pour les inclure # Collecter les commandes directes validées AVANT génération des documents (pour les inclure
# dans l'état de travaux/Excel et les réinitialiser seulement après) # dans l'état de travaux/Excel et les réinitialiser seulement après)
@ -1454,20 +1454,42 @@ def generate_documents_for_order(request, order_id):
) )
# Mettre à jour le statut des interventions : # Mettre à jour le statut des interventions :
now = timezone.now()
event_user = request.user if request.user.is_authenticated else None
# 1. Toutes les interventions validées (même sans postes validés) # 1. Toutes les interventions validées (même sans postes validés)
validated_interventions = order.interventions.filter(status='validated') validated_interventions = order.interventions.filter(status='validated')
for intervention in validated_interventions: for intervention in validated_interventions:
old_status = intervention.status
intervention.status = 'invoiced' intervention.status = 'invoiced'
intervention.save() intervention.save()
InterventionTimeLine.objects.create(
intervention=intervention,
event_user=event_user,
event_time=now,
event_type='status_change',
from_status=old_status,
to_status='invoiced',
)
# 2. Toutes les interventions avec périodes validées (quel que soit leur statut) # 2. Toutes les interventions avec périodes validées (quel que soit leur statut)
intervention_ids_with_validated_periods = validated_periods.values_list('intervention_id', flat=True).distinct() intervention_ids_with_validated_periods = validated_periods.values_list('intervention_id', flat=True).distinct()
interventions_with_periods = order.interventions.filter(id__in=intervention_ids_with_validated_periods) interventions_with_periods = order.interventions.filter(
id__in=intervention_ids_with_validated_periods
).exclude(status='invoiced')
for intervention in interventions_with_periods: for intervention in interventions_with_periods:
# Passer à 'invoiced' seulement si pas déjà dans cet état
if intervention.status != 'invoiced': if intervention.status != 'invoiced':
old_status = intervention.status
intervention.status = 'invoiced' intervention.status = 'invoiced'
intervention.save() intervention.save()
InterventionTimeLine.objects.create(
intervention=intervention,
event_user=event_user,
event_time=now,
event_type='status_change',
from_status=old_status,
to_status='invoiced',
)
# Marquer les périodes validées comme 'invoiced' # Marquer les périodes validées comme 'invoiced'
validated_periods.update(status='invoiced') validated_periods.update(status='invoiced')