refactor: restrict contract blocked warning to specific intervention statuses and add test coverage

This commit is contained in:
kdeterme 2026-09-04 11:06:31 +02:00
parent 7f257a2ca3
commit 3a241e3d03
6 changed files with 77 additions and 10 deletions

View file

@ -738,11 +738,21 @@ class Intervention(models.Model):
return self.assigned_team.company.name
return ""
BLOCKED_CONTRACT_DISPLAY_STATUSES = ('in_preparation', 'to_be_approved', 'to_be_planned')
@property
def is_contract_blocked(self):
"""Retourne True si l'intervention est marquée bloquée ou si son contrat est actuellement bloqué."""
return bool(self.is_blocked_due_to_contract or (self.contract and getattr(self.contract, 'is_blocked', False)))
@property
def show_contract_blocked_warning(self):
"""
Indique si l'avertissement / badge de marché bloqué doit être affiché.
Uniquement affiché pour les interventions en préparation, à approuver ou à planifier.
"""
return bool(self.is_contract_blocked and self.status in self.BLOCKED_CONTRACT_DISPLAY_STATUSES)
def get_status_color(self):
return {
"in_preparation": "secondary",

View file

@ -496,8 +496,9 @@ function statusFormatter(value, row) {
html += `&nbsp;<span class="badge text-bg-${prepEntry.color}" title="${prepEntry.label}"><i class="bi ${prepEntry.icon}"></i></span>`;
}
// Add blocked due to contract indicator if present
if (row && (row.is_blocked_due_to_contract || (row.contract && row.contract.is_blocked))) {
// Add blocked due to contract indicator if present (only for in_preparation, to_be_approved, to_be_planned)
const blockedStatuses = ['in_preparation', 'to_be_approved', 'to_be_planned'];
if (row && (row.is_blocked_due_to_contract || (row.contract && row.contract.is_blocked)) && blockedStatuses.includes(row.status || value)) {
html += `&nbsp;<span class="badge text-bg-danger" title="${gettext('Marché temporairement bloqué')}"><i class="bi bi-slash-circle"></i></span>`;
}

View file

@ -52,7 +52,7 @@
<a href="{% url 'mobile:intervention_detail_mobile' intervention.id %}" class="me-2">{{ intervention.code }}</a> {{ intervention.title }}
</h6>
<span class="badge bg-{{ intervention.get_status_color }} status-{{ intervention.status }}">{{ intervention.get_status_display }}</span>
{% if intervention.is_contract_blocked %}
{% if intervention.show_contract_blocked_warning %}
<span class="badge bg-danger" title="{% translate 'Marché temporairement bloqué' %}">
<i class="bi bi-slash-circle"></i> {% translate "Marché bloqué" %}
</span>

View file

@ -31,7 +31,7 @@
{% endif %}
{% endif %}
<span class="field-value" style="color: var(--brand-color);">{{ intervention.title }}</span>
{% if intervention.is_contract_blocked %}
{% if intervention.show_contract_blocked_warning %}
<span class="badge bg-danger text-white ms-2" title="{% translate 'Marché temporairement bloqué pour raisons budgétaires' %}">
<i class="bi bi-slash-circle"></i> {% translate "Marché bloqué" %}
</span>
@ -84,7 +84,7 @@
{% endif %}
</div>
{% if intervention.is_contract_blocked %}
{% if intervention.show_contract_blocked_warning %}
<div class="alert alert-danger d-flex align-items-center mt-3 mb-2 py-2 px-3 shadow-sm border-danger" role="alert">
<i class="bi bi-slash-circle-fill fs-4 me-2 text-danger"></i>
<div>
@ -107,7 +107,7 @@
<h4>{% translate "Recommandations" %}</h4>
</div>
<div class="card-body">
{% if intervention.is_contract_blocked %}
{% if intervention.show_contract_blocked_warning %}
<div class="alert alert-warning d-flex align-items-center mb-3 py-2 px-3">
<i class="bi bi-telephone-x-fill fs-4 me-2 text-danger"></i>
<div>
@ -121,7 +121,7 @@
<li class="list-group-item list-group-item-info d-flex justify-content-between align-items-center">
<div>
<i class="bi {{recommendation.icon}} me-2"></i>
{% if intervention.is_contract_blocked and recommendation.confirm_call %}
{% if intervention.show_contract_blocked_warning and recommendation.confirm_call %}
<span class="text-decoration-line-through text-muted">
{% render_recommendation recommendation intervention.contract as rendered_text %}
{{ rendered_text|safe }}
@ -132,7 +132,7 @@
{{ rendered_text|safe }}
{% endif %}
</div>
{% if recommendation.confirm_call and not intervention.is_contract_blocked %}
{% if recommendation.confirm_call and not intervention.show_contract_blocked_warning %}
<button class="btn btn-outline-success btn-sm confirm-call"
data-call-id="{{recommendation.order_number}}"
data-confirmed-at="{{ intervention.init_call_confirmed_time|date:'c' }}"

View file

@ -472,7 +472,7 @@
</div>
{% endif %}
{% if intervention.is_contract_blocked %}
{% if intervention.show_contract_blocked_warning %}
<div class="alert alert-danger d-flex align-items-center mb-3 py-2 px-3 shadow-sm border-danger" role="alert">
<i class="bi bi-slash-circle-fill fs-4 me-2 text-danger"></i>
<div>
@ -488,7 +488,7 @@
<div class="d-flex justify-content-between align-items-center mb-2 flex-wrap gap-2">
<div class="code d-flex align-items-center gap-2">
<span><i class="bi bi-tools"></i> {{ intervention.code }}</span>
{% if intervention.is_contract_blocked %}
{% if intervention.show_contract_blocked_warning %}
<span class="badge bg-danger text-white ms-1" title="{% translate 'Marché bloqué' %}">
<i class="bi bi-slash-circle"></i> {% translate "Marché bloqué" %}
</span>

View file

@ -255,3 +255,59 @@ class BlockedContractsTests(TestCase):
itv.refresh_from_db()
self.assertEqual(itv.status, 'to_be_processed')
def test_show_contract_blocked_warning_only_for_prep_approval_planning(self):
"""
Vérifie que show_contract_blocked_warning est True uniquement pour :
- in_preparation
- to_be_approved
- to_be_planned
Et False pour tous les autres statuts (ex: validated, finished, in_progress, etc.).
"""
allowed_warning_statuses = {'in_preparation', 'to_be_approved', 'to_be_planned'}
other_statuses = {
'to_be_processed', 'assigned', 'in_progress', 'on_pause', 'finished',
'processed', 'to_be_corrected', 'corrected', 'validated', 'invoiced',
'closed', 'canceled'
}
# Pour une intervention sur contrat bloqué
for status in allowed_warning_statuses:
itv = Intervention(
title=f'Test {status}',
contract=self.contract_blocked,
status=status,
thematic=self.thematic,
asset_category=self.category,
)
self.assertTrue(
itv.show_contract_blocked_warning,
f"show_contract_blocked_warning devrait être True pour le statut {status}"
)
for status in other_statuses:
itv = Intervention(
title=f'Test {status}',
contract=self.contract_blocked,
status=status,
thematic=self.thematic,
asset_category=self.category,
)
self.assertFalse(
itv.show_contract_blocked_warning,
f"show_contract_blocked_warning devrait être False pour le statut {status}"
)
# Pour une intervention sur contrat actif (non bloqué)
for status in allowed_warning_statuses | other_statuses:
itv = Intervention(
title=f'Test {status} active',
contract=self.contract_active,
status=status,
thematic=self.thematic,
asset_category=self.category,
)
self.assertFalse(
itv.show_contract_blocked_warning,
f"show_contract_blocked_warning devrait être False sur contrat actif pour le statut {status}"
)