Filtrer le tableau de bord selon les permissions
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run

This commit is contained in:
root 2026-08-21 17:35:46 +00:00
parent 699d094796
commit ee8c0ad5a0
2 changed files with 128 additions and 90 deletions

View file

@ -13,6 +13,7 @@ from ..models.college import Building, Room
from ..models.company import Part
from app_new.constants import INTERVENTION_STATUSES, EQUIPMENT_STATUSES
from sqlalchemy import func
from ..authorization import has_permission
dashboard_bp = Blueprint('dashboard', __name__)
@ -26,19 +27,32 @@ def index():
from ..models.planning import ScheduledTask, AdminTask
from ..models.maintenance import Lot
dashboard_permissions = {
'intervention': has_permission('intervention.view', current_user),
'intervention_manage': has_permission('intervention.manage', current_user),
'patrimoine': has_permission('patrimoine.view', current_user),
'planning': has_permission('planning.view', current_user),
'planning_manage': has_permission('planning.manage', current_user),
'stock': has_permission('stock.view', current_user),
'prevention': has_permission('prevention.view', current_user),
'ent': has_permission('integration.ent.view', current_user),
'outlook': has_permission('integration.outlook.view', current_user),
'ai': has_permission('system.configure', current_user),
}
# Statistiques
stats = {
'interventions_total': Intervention.query.count(),
'interventions_en_cours': Intervention.query.filter_by(status='en_cours').count(),
'interventions_en_attente': Intervention.query.filter_by(status='en_attente').count(),
'interventions_urgentes': Intervention.query.filter_by(priority='urgente', is_deleted=False).count(),
'equipments_total': Equipment.query.count(),
'equipments_actifs': Equipment.query.filter_by(status='en_service', is_deleted=False).count(),
'equipments_panne': Equipment.query.filter(Equipment.status.in_(['en_panne', 'hors_service', 'hs']), Equipment.is_deleted.is_(False)).count(),
'buildings_total': Building.query.count(),
'rooms_total': Room.query.count(),
'companies_total': Company.query.count(),
'parts_low_stock': Part.query.filter(Part.quantity <= Part.min_quantity).count(),
'interventions_total': Intervention.query.count() if dashboard_permissions['intervention'] else 0,
'interventions_en_cours': Intervention.query.filter_by(status='en_cours').count() if dashboard_permissions['intervention'] else 0,
'interventions_en_attente': Intervention.query.filter_by(status='en_attente').count() if dashboard_permissions['intervention'] else 0,
'interventions_urgentes': Intervention.query.filter_by(priority='urgente', is_deleted=False).count() if dashboard_permissions['intervention'] else 0,
'equipments_total': Equipment.query.count() if dashboard_permissions['patrimoine'] else 0,
'equipments_actifs': Equipment.query.filter_by(status='en_service', is_deleted=False).count() if dashboard_permissions['patrimoine'] else 0,
'equipments_panne': Equipment.query.filter(Equipment.status.in_(['en_panne', 'hors_service', 'hs']), Equipment.is_deleted.is_(False)).count() if dashboard_permissions['patrimoine'] else 0,
'buildings_total': Building.query.count() if dashboard_permissions['patrimoine'] else 0,
'rooms_total': Room.query.count() if dashboard_permissions['patrimoine'] else 0,
'companies_total': Company.query.count() if dashboard_permissions['intervention'] else 0,
'parts_low_stock': Part.query.filter(Part.quantity <= Part.min_quantity).count() if dashboard_permissions['stock'] else 0,
}
# Contrôle des lots : un lot présent doit être rattaché à au moins un
@ -52,8 +66,9 @@ def index():
)
.group_by(Equipment.lot_id)
.all()
if dashboard_permissions['patrimoine'] else {}
)
lots = Lot.query.order_by(Lot.name).all()
lots = Lot.query.order_by(Lot.name).all() if dashboard_permissions['patrimoine'] else []
lots_without_equipment = [
lot for lot in lots
if lot.is_present and equipment_counts.get(lot.id, 0) == 0
@ -77,14 +92,14 @@ def index():
stats['lots_missing_duration'] = len(lots_missing_duration)
# Dernières interventions
recent_interventions = Intervention.query.order_by(Intervention.created_at.desc()).limit(10).all()
recent_interventions = Intervention.query.order_by(Intervention.created_at.desc()).limit(10).all() if dashboard_permissions['intervention'] else []
# Interventions urgentes
urgent_interventions = Intervention.query.filter_by(priority='urgente', is_deleted=False).limit(5).all()
urgent_interventions = Intervention.query.filter_by(priority='urgente', is_deleted=False).limit(5).all() if dashboard_permissions['intervention'] else []
# Interventions du jour (planning)
today = date.today()
day_schedule = PlanningService.get_day_schedule(today)
day_schedule = PlanningService.get_day_schedule(today) if dashboard_permissions['planning'] else None
# Interprétations en attente (non traitées)
from ...outlook.models import OutlookMailInterpretation
@ -94,12 +109,12 @@ def index():
outlook_interpretations = OutlookMailInterpretation.query.filter_by(
status='pending',
user_id=current_user.id
).order_by(OutlookMailInterpretation.created_at.desc()).limit(10).all()
).order_by(OutlookMailInterpretation.created_at.desc()).limit(10).all() if dashboard_permissions['outlook'] else []
# Récupérer les interprétations ENT
ent_interpretations = EntMessageInterpretation.query.filter_by(
status='pending'
).order_by(EntMessageInterpretation.created_at.desc()).limit(10).all()
).order_by(EntMessageInterpretation.created_at.desc()).limit(10).all() if dashboard_permissions['ent'] else []
# Combiner les deux listes avec une source
pending_interpretations = []
@ -148,7 +163,8 @@ def index():
lots_without_equipment=lots_without_equipment,
lots_missing_duration=lots_missing_duration,
today=today,
statuses=INTERVENTION_STATUSES)
statuses=INTERVENTION_STATUSES,
dashboard_permissions=dashboard_permissions)
@dashboard_bp.route('/api/stats')
@ -158,28 +174,33 @@ def api_stats():
from datetime import date, timedelta
from sqlalchemy import func
can_intervention = has_permission('intervention.view', current_user)
can_patrimoine = has_permission('patrimoine.view', current_user)
# Interventions par mois (6 derniers mois)
six_months_ago = date.today() - timedelta(days=180)
monthly = db.session.query(
func.date_format(Intervention.created_at, '%Y-%m').label('month'),
func.count().label('count')
).filter(Intervention.created_at >= six_months_ago).group_by('month').order_by('month').all()
).filter(Intervention.created_at >= six_months_ago).group_by('month').order_by('month').all() if can_intervention else []
monthly_data = [{'month': r.month, 'count': r.count} for r in monthly]
# Interventions par statut
status_data = {}
for status_val, info in INTERVENTION_STATUSES.items():
count = Intervention.query.filter_by(status=status_val).count()
if count > 0:
status_data[info['label']] = count
if can_intervention:
for status_val, info in INTERVENTION_STATUSES.items():
count = Intervention.query.filter_by(status=status_val).count()
if count > 0:
status_data[info['label']] = count
# Equipements par statut
equip_status = {}
for s in EQUIPMENT_STATUSES:
count = Equipment.query.filter_by(status=s).count()
if count > 0:
equip_status[s] = count
if can_patrimoine:
for s in EQUIPMENT_STATUSES:
count = Equipment.query.filter_by(status=s).count()
if count > 0:
equip_status[s] = count
# Top 5 equipements les plus intervenus
top_equip = db.session.query(
@ -188,7 +209,7 @@ def api_stats():
).join(Intervention, Intervention.equipment_id == Equipment.id)\
.group_by(Equipment.id)\
.order_by(func.count(Intervention.id).desc())\
.limit(5).all()
.limit(5).all() if can_intervention and can_patrimoine else []
top_data = [{'name': r.name, 'count': r.count} for r in top_equip]
@ -213,26 +234,26 @@ def search():
}
if query:
# Recherche interventions
results['interventions'] = Intervention.query.filter(
db.or_(
Intervention.title.ilike(f'%{query}%'),
Intervention.description.ilike(f'%{query}%')
)
).limit(10).all()
if has_permission('intervention.view', current_user):
results['interventions'] = Intervention.query.filter(
db.or_(
Intervention.title.ilike(f'%{query}%'),
Intervention.description.ilike(f'%{query}%')
)
).limit(10).all()
# Recherche équipements
results['equipments'] = Equipment.query.filter(
db.or_(
Equipment.name.ilike(f'%{query}%'),
Equipment.serial_number.ilike(f'%{query}%')
)
).limit(10).all()
if has_permission('patrimoine.view', current_user):
results['equipments'] = Equipment.query.filter(
db.or_(
Equipment.name.ilike(f'%{query}%'),
Equipment.serial_number.ilike(f'%{query}%')
)
).limit(10).all()
# Recherche salles
results['rooms'] = Room.query.filter(
Room.name.ilike(f'%{query}%')
).limit(10).all()
if has_permission('patrimoine.view', current_user):
results['rooms'] = Room.query.filter(
Room.name.ilike(f'%{query}%')
).limit(10).all()
return render_template('dashboard/search.html', query=query, results=results)
@ -245,31 +266,36 @@ def alerts():
from app_new.core.models.company import Alert
alerts_list = Alert.query.filter_by(is_read=False).order_by(Alert.created_at.desc()).all()
can_intervention = has_permission('intervention.view', current_user)
can_patrimoine = has_permission('patrimoine.view', current_user)
can_outlook = has_permission('integration.outlook.view', current_user)
can_ent = has_permission('integration.ent.view', current_user)
# Interventions urgentes (statut haute priorité)
urgent_interventions = Intervention.query.filter(
Intervention.priority == 'haute',
Intervention.status.in_(['planifiee', 'en_cours', 'en_attente'])
).order_by(Intervention.scheduled_date.desc()).all()
).order_by(Intervention.scheduled_date.desc()).all() if can_intervention else []
# Équipements en panne
from app_new.core.models.equipment import Equipment
equipments_panne = Equipment.query.filter(
Equipment.status.in_(['en_panne', 'hors_service', 'hs']),
Equipment.is_deleted.is_(False),
).all()
).all() if can_patrimoine else []
# Équipements à jeter
equipments_to_trash = Equipment.query.filter_by(status='a_jeter').all()
equipments_to_trash = Equipment.query.filter_by(status='a_jeter').all() if can_patrimoine else []
# Interprétations en attente
from ...outlook.models import OutlookMailInterpretation
from ...ent.interpretation_models import EntMessageInterpretation
pending_outlook = OutlookMailInterpretation.query.filter_by(
status='pending', user_id=current_user.id
).order_by(OutlookMailInterpretation.created_at.desc()).all()
).order_by(OutlookMailInterpretation.created_at.desc()).all() if can_outlook else []
pending_ent = EntMessageInterpretation.query.filter_by(
status='pending'
).order_by(EntMessageInterpretation.created_at.desc()).all()
).order_by(EntMessageInterpretation.created_at.desc()).all() if can_ent else []
return render_template('dashboard/alerts.html',
alerts=alerts_list,
@ -302,6 +328,10 @@ def create_month_interventions():
from ..models.company import Company
from flask_login import current_user
if not (has_permission('planning.manage', current_user) or has_permission('intervention.manage', current_user)):
from flask import abort
abort(403)
today = date.today()
one_month_later = today + timedelta(days=30)

View file

@ -11,33 +11,35 @@
{% block content %}
<h1 class="mb-3 h3"><i class="bi bi-speedometer2"></i> Tableau de bord</h1>
<!-- Alerte modele IA -->
{% if dashboard_permissions.ai %}<!-- Alerte modele IA -->
<div id="ai-alert" style="display:none;" class="alert alert-danger mb-3">
<i class="bi bi-exclamation-triangle"></i>
<strong>Modele IA indisponible !</strong>
<span id="ai-alert-msg"></span>
<a href="{{ url_for('ai_config.index') }}" class="alert-link">Configurer le modele</a>
</div>
{% endif %}
<!-- Alerte retards -->
{% if dashboard_permissions.planning %}<!-- Alerte retards -->
<div id="overdue-alert" style="display:none;" class="alert alert-warning mb-3">
<i class="bi bi-clock-history"></i>
<strong>Retards detectes !</strong>
<span id="overdue-alert-msg"></span>
<a href="{{ url_for('scheduler.index') }}" class="alert-link">Voir le planificateur</a>
</div>
{% endif %}
<!-- Stats rapides -->
<div class="row g-2 g-md-3 mb-3">
<div class="col-12">
{% if dashboard_permissions.intervention_manage or dashboard_permissions.planning_manage %}<div class="col-12">
<form method="POST" action="{{ url_for('dashboard.create_month_interventions') }}" class="d-inline" onsubmit="return confirm('Créer les interventions pour le mois prochain?')">
<button type="submit" class="btn btn-primary btn-sm">
<i class="bi bi-calendar-plus me-1"></i>Créer interventions (1 mois)
</button>
</form>
</div>
</div>{% endif %}
<div class="col-6 col-md-3">
{% if dashboard_permissions.intervention %}<div class="col-6 col-md-3">
<div class="card stat-card text-white bg-primary">
<div class="card-body py-2 px-3">
<small class="card-title">Interventions</small>
@ -45,8 +47,8 @@
<small>{{ stats.interventions_en_cours }} en cours</small>
</div>
</div>
</div>
<div class="col-6 col-md-3">
</div>{% endif %}
{% if dashboard_permissions.intervention %}<div class="col-6 col-md-3">
<div class="card stat-card text-white bg-success">
<div class="card-body py-2 px-3">
<small class="card-title">Curatif / Préventif</small>
@ -54,8 +56,8 @@
<small>{{ stats.interventions_recurrentes }} récurrentes</small>
</div>
</div>
</div>
<div class="col-6 col-md-3">
</div>{% endif %}
{% if dashboard_permissions.patrimoine %}<div class="col-6 col-md-3">
<div class="card stat-card text-white bg-warning">
<div class="card-body py-2 px-3">
<small class="card-title">Équipements</small>
@ -63,8 +65,8 @@
<small>{{ stats.equipments_hors_service }} hors service</small>
</div>
</div>
</div>
<div class="col-6 col-md-3">
</div>{% endif %}
{% if dashboard_permissions.stock %}<div class="col-6 col-md-3">
<div class="card stat-card {% if stats.parts_low_stock > 0 %}bg-danger{% else %}bg-info{% endif %} text-white">
<div class="card-body py-2 px-3">
<small class="card-title">Stock bas</small>
@ -72,10 +74,10 @@
<small>pièce(s)</small>
</div>
</div>
</div>
</div>{% endif %}
</div>
<!-- Échéances préventif -->
{% if dashboard_permissions.planning %}<!-- Échéances préventif -->
<div class="row g-2 g-md-3 mb-3">
<div class="col-6 col-md-3">
<a href="{{ url_for('interventions_planning.planning') }}" class="text-decoration-none">
@ -117,9 +119,9 @@
</div>
</a>
</div>
</div>
</div>{% endif %}
<!-- Contrôles des référentiels de maintenance -->
{% if dashboard_permissions.patrimoine %}<!-- Contrôles des référentiels de maintenance -->
<div class="row g-2 g-md-3 mb-3">
<div class="col-lg-6">
<div class="card h-100 {% if stats.lots_without_equipment %}border-warning{% else %}border-success{% endif %}">
@ -177,9 +179,9 @@
</div>
</div>
</div>
</div>
</div>{% endif %}
<!-- Alertes -->
{% if dashboard_permissions.intervention or dashboard_permissions.patrimoine or dashboard_permissions.ent or dashboard_permissions.outlook %}<!-- Alertes -->
{% if alerts %}
<div class="card border-warning mb-3">
<div class="card-header bg-warning text-dark py-2">
@ -196,9 +198,9 @@
{% endif %}
</div>
</div>
{% endif %}
{% endif %}{% endif %}
<!-- Maintenances du jour -->
{% if dashboard_permissions.planning %}<!-- Maintenances du jour -->
{% if day_schedule and day_schedule.items %}
<div class="card border-primary mb-3">
<div class="card-header bg-primary text-white py-2">
@ -299,9 +301,9 @@
<p class="mb-0">{{ day_schedule.leave }}</p>
</div>
</div>
{% endif %}
{% endif %}{% endif %}
<!-- 2 colonnes : Maintenances + Fréquence -->
{% if dashboard_permissions.planning %}<!-- 2 colonnes : Maintenances + Fréquence -->
<div class="row g-2 g-md-3 mb-3">
<!-- Maintenances à venir -->
<div class="col-12 col-md-6">
@ -376,9 +378,9 @@
</div>
</div>
</div>
</div>
</div>{% endif %}
<!-- Workflow + Activité récente -->
{% if dashboard_permissions.intervention %}<!-- Workflow + Activité récente -->
<div class="row g-2 g-md-3 mb-3">
<div class="col-12 col-md-6">
<div class="card h-100 border-warning">
@ -433,39 +435,40 @@
</div>
</div>
</div>
</div>
</div>{% endif %}
<!-- Graphiques -->
{% if dashboard_permissions.intervention or dashboard_permissions.patrimoine %}<!-- Graphiques -->
<div class="row g-3 mb-3">
<div class="col-md-6">
{% if dashboard_permissions.intervention %}<div class="col-md-6">
<div class="card">
<div class="card-header"><h6 class="mb-0"><i class="bi bi-bar-chart"></i> Interventions par mois</h6></div>
<div class="card-body"><canvas id="chart-monthly" height="200"></canvas></div>
</div>
</div>
<div class="col-md-6">
</div>{% endif %}
{% if dashboard_permissions.intervention %}<div class="col-md-6">
<div class="card">
<div class="card-header"><h6 class="mb-0"><i class="bi bi-pie-chart"></i> Interventions par statut</h6></div>
<div class="card-body"><canvas id="chart-status" height="200"></canvas></div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
</div>{% endif %}
</div>{% endif %}
{% if dashboard_permissions.patrimoine or (dashboard_permissions.intervention and dashboard_permissions.patrimoine) %}<div class="row g-3 mb-3">
{% if dashboard_permissions.patrimoine %}<div class="col-md-6">
<div class="card">
<div class="card-header"><h6 class="mb-0"><i class="bi bi-pie-chart"></i> Equipements par statut</h6></div>
<div class="card-body"><canvas id="chart-equip" height="200"></canvas></div>
</div>
</div>
<div class="col-md-6">
</div>{% endif %}
{% if dashboard_permissions.intervention and dashboard_permissions.patrimoine %}<div class="col-md-6">
<div class="card">
<div class="card-header"><h6 class="mb-0"><i class="bi bi-bar-chart"></i> Top 5 equipements</h6></div>
<div class="card-body"><canvas id="chart-top" height="200"></canvas></div>
</div>
</div>
</div>{% endif %}
</div>
{% endif %}
<!-- Dernières interventions -->
{% if dashboard_permissions.intervention %}<!-- Dernières interventions -->
<div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center py-2">
<span><i class="bi bi-clock-history"></i> Dernières interventions</span>
@ -516,10 +519,10 @@
<div class="p-3 text-muted">Aucune intervention.</div>
{% endif %}
</div>
</div>
</div>{% endif %}
<!-- Interprétations en attente -->
{% if pending_interpretations %}
{% if pending_interpretations and (dashboard_permissions.ent or dashboard_permissions.outlook) %}
<div class="card mb-3 border-warning">
<div class="card-header bg-warning bg-opacity-10 d-flex justify-content-between align-items-center py-2">
<span><i class="bi bi-robot"></i> Interprétations en attente <span class="badge bg-warning text-dark">{{ pending_interpretations|length }}</span></span>
@ -684,7 +687,8 @@ document.addEventListener('DOMContentLoaded', function() {
});
</script>
<script>
{% if dashboard_permissions.ai or dashboard_permissions.planning %}<script>
{% if dashboard_permissions.ai %}
// Verifier le statut du modele IA au chargement
fetch('/ai-config/api/status').then(r => r.json()).then(d => {
if (d.status === 'error' || d.status === 'no_model' || d.status === 'no_key') {
@ -700,7 +704,9 @@ fetch('/ai-config/api/status').then(r => r.json()).then(d => {
alert.style.display = 'block';
}
}).catch(() => {});
{% endif %}
{% if dashboard_permissions.planning %}
// Verifier les retards
fetch('/scheduler/api/overdue-count').then(r => r.json()).then(d => {
const total = (d.overdue_tasks || 0) + (d.overdue_interventions || 0);
@ -711,5 +717,7 @@ fetch('/scheduler/api/overdue-count').then(r => r.json()).then(d => {
alert.style.display = 'block';
}
}).catch(() => {});
{% endif %}
</script>
{% endif %}
{% endblock %}