feat: restrict intervention timeline history and filter controls to internal users
This commit is contained in:
parent
2df4621e5b
commit
7911369740
3 changed files with 54 additions and 1 deletions
|
|
@ -13,7 +13,8 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filtre mode d'affichage -->
|
<!-- Filtre mode d'affichage (réservé aux agents internes) -->
|
||||||
|
{% if is_user_intern %}
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<div class="btn-group btn-group-sm" role="group" aria-label="{% translate 'Filtrer l\'historique' %}">
|
<div class="btn-group btn-group-sm" role="group" aria-label="{% translate 'Filtrer l\'historique' %}">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
|
|
@ -35,6 +36,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body p-3">
|
<div class="card-body p-3">
|
||||||
|
|
@ -42,6 +44,7 @@
|
||||||
{% if events_list %}
|
{% if events_list %}
|
||||||
<div class="history-timeline">
|
<div class="history-timeline">
|
||||||
{% for event in events_list %}
|
{% for event in events_list %}
|
||||||
|
{% if is_user_intern or event.is_default_visible %}
|
||||||
<div class="history-timeline-item {% if not event.is_default_visible %}history-item-detailed history-item-modification d-none{% endif %}"
|
<div class="history-timeline-item {% if not event.is_default_visible %}history-item-detailed history-item-modification d-none{% endif %}"
|
||||||
data-event-type="{{ event.event_type }}">
|
data-event-type="{{ event.event_type }}">
|
||||||
<!-- Node Icon -->
|
<!-- Node Icon -->
|
||||||
|
|
@ -144,6 +147,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
||||||
|
|
@ -477,5 +477,47 @@ class TimelineHistoryTests(TestCase):
|
||||||
self.assertContains(response, 'history-status-circle')
|
self.assertContains(response, 'history-status-circle')
|
||||||
self.assertContains(response, 'history-status-transition')
|
self.assertContains(response, 'history-status-transition')
|
||||||
self.assertContains(response, 'bg-purple')
|
self.assertContains(response, 'bg-purple')
|
||||||
|
self.assertContains(response, 'id="btnHistoryFull"')
|
||||||
|
|
||||||
|
def test_detail_view_hides_full_history_for_external_user(self):
|
||||||
|
"""Test that external users (is_intern=False) do NOT see full history or detailed modification/note events."""
|
||||||
|
User = get_user_model()
|
||||||
|
ext_user = User.objects.create_user(
|
||||||
|
username='prestataire_ext',
|
||||||
|
password='secret_password',
|
||||||
|
first_name='Pierre',
|
||||||
|
last_name='Martin'
|
||||||
|
)
|
||||||
|
ext_config = UserConfig.objects.create(user=ext_user, is_intern=False)
|
||||||
|
ext_role, _ = Role.objects.get_or_create(name='external_manager')
|
||||||
|
ext_config.roles.add(ext_role)
|
||||||
|
self.intervention.assigned_provider = self.company
|
||||||
|
self.intervention.save()
|
||||||
|
|
||||||
|
record_timeline_event(self.intervention, self.user, event_type='status_change', from_status='in_preparation', to_status='to_be_processed')
|
||||||
|
record_note_event(self.intervention, self.user, action='create', note_type='comment', content='Note interne confidentielle')
|
||||||
|
record_document_event(self.intervention, self.user, action='upload', filenames=['secret_doc.pdf'])
|
||||||
|
|
||||||
|
self.client.login(username='prestataire_ext', password='secret_password')
|
||||||
|
url = reverse('interventions:intervention_detail', args=[self.intervention.id])
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# Le filtre "Historique complet" ne doit PAS être affiché
|
||||||
|
self.assertNotContains(response, 'id="btnHistoryFull"')
|
||||||
|
self.assertNotContains(response, 'id="btnHistoryDefault"')
|
||||||
|
|
||||||
|
# Aucun événement détaillé n'est dans le contexte
|
||||||
|
self.assertEqual(response.context['timeline_detailed_count'], 0)
|
||||||
|
self.assertTrue(all(e.is_default_visible for e in response.context['timeline_events']))
|
||||||
|
|
||||||
|
# Le contenu confidentiel des notes et documents n'est pas transmis dans l'historique
|
||||||
|
self.assertNotContains(response, 'Note interne confidentielle')
|
||||||
|
self.assertNotContains(response, 'secret_doc.pdf')
|
||||||
|
|
||||||
|
# Les statuts et assignations restent parfaitement visibles
|
||||||
|
self.assertContains(response, 'history-status-circle')
|
||||||
|
self.assertContains(response, 'history-status-transition')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -682,6 +682,13 @@ def intervention_detail(request, intervention_id):
|
||||||
insert_idx = 1 if (timeline_events and timeline_events[0].is_creation) else 0
|
insert_idx = 1 if (timeline_events and timeline_events[0].is_creation) else 0
|
||||||
timeline_events.insert(insert_idx, synth_event)
|
timeline_events.insert(insert_idx, synth_event)
|
||||||
|
|
||||||
|
# Sécurité / Confidentialité : l'historique complet (modifications d'attributs, notes, documents, liens)
|
||||||
|
# est strictement réservé aux agents internes.
|
||||||
|
# Les utilisateurs externes (prestataires) ne reçoivent que le flux opérationnel
|
||||||
|
# (statuts, assignations, créations, transferts).
|
||||||
|
if not is_user_intern:
|
||||||
|
timeline_events = [e for e in timeline_events if e.is_default_visible]
|
||||||
|
|
||||||
timeline_default_count = sum(1 for e in timeline_events if e.is_default_visible)
|
timeline_default_count = sum(1 for e in timeline_events if e.is_default_visible)
|
||||||
timeline_detailed_count = sum(1 for e in timeline_events if not e.is_default_visible)
|
timeline_detailed_count = sum(1 for e in timeline_events if not e.is_default_visible)
|
||||||
timeline_modification_count = timeline_detailed_count
|
timeline_modification_count = timeline_detailed_count
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue