Ajouter les alertes de lots au tableau de bord
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
This commit is contained in:
parent
f0c3dafd25
commit
a18e0f2713
2 changed files with 100 additions and 1 deletions
|
|
@ -12,6 +12,7 @@ from ..models.company import Company
|
|||
from ..models.college import Building, Room
|
||||
from ..models.company import Part
|
||||
from app_new.constants import INTERVENTION_STATUSES, EQUIPMENT_STATUSES
|
||||
from sqlalchemy import func
|
||||
|
||||
dashboard_bp = Blueprint('dashboard', __name__)
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ def index():
|
|||
from datetime import date
|
||||
from ..services.planning_service import PlanningService
|
||||
from ..models.planning import ScheduledTask, AdminTask
|
||||
from ..models.maintenance import Lot
|
||||
|
||||
# Statistiques
|
||||
stats = {
|
||||
|
|
@ -38,6 +40,41 @@ def index():
|
|||
'companies_total': Company.query.count(),
|
||||
'parts_low_stock': Part.query.filter(Part.quantity <= Part.min_quantity).count(),
|
||||
}
|
||||
|
||||
# Contrôle des lots : un lot présent doit être rattaché à au moins un
|
||||
# équipement, sauf s'il a été explicitement marqué « non présent ».
|
||||
equipment_counts = dict(
|
||||
db.session.query(Equipment.lot_id, func.count(Equipment.id))
|
||||
.filter(
|
||||
Equipment.lot_id.isnot(None),
|
||||
Equipment.is_deleted.is_(False),
|
||||
db.or_(Equipment.status.is_(None), Equipment.status != 'jete'),
|
||||
)
|
||||
.group_by(Equipment.lot_id)
|
||||
.all()
|
||||
)
|
||||
lots = Lot.query.order_by(Lot.name).all()
|
||||
lots_without_equipment = [
|
||||
lot for lot in lots
|
||||
if lot.is_present and equipment_counts.get(lot.id, 0) == 0
|
||||
]
|
||||
lots_missing_duration = []
|
||||
for lot in lots:
|
||||
if not lot.is_present or equipment_counts.get(lot.id, 0) == 0:
|
||||
continue
|
||||
active_tasks = [task for task in lot.tasks if task.is_active]
|
||||
missing_tasks = [
|
||||
task for task in lot.tasks
|
||||
if task.is_active and not task.duree_minutes
|
||||
]
|
||||
if missing_tasks or not active_tasks:
|
||||
lots_missing_duration.append({
|
||||
'lot': lot,
|
||||
'tasks': missing_tasks,
|
||||
'no_task': not active_tasks,
|
||||
})
|
||||
stats['lots_without_equipment'] = len(lots_without_equipment)
|
||||
stats['lots_missing_duration'] = len(lots_missing_duration)
|
||||
|
||||
# Dernières interventions
|
||||
recent_interventions = Intervention.query.order_by(Intervention.created_at.desc()).limit(10).all()
|
||||
|
|
@ -108,6 +145,8 @@ def index():
|
|||
urgent_interventions=urgent_interventions,
|
||||
pending_interpretations=pending_interpretations,
|
||||
day_schedule=day_schedule,
|
||||
lots_without_equipment=lots_without_equipment,
|
||||
lots_missing_duration=lots_missing_duration,
|
||||
today=today,
|
||||
statuses=INTERVENTION_STATUSES)
|
||||
|
||||
|
|
|
|||
|
|
@ -119,6 +119,66 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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 %}">
|
||||
<div class="card-header {% if stats.lots_without_equipment %}bg-warning text-dark{% else %}bg-success text-white{% endif %} py-2">
|
||||
<i class="bi bi-box-seam me-1"></i>Lots présents sans équipement
|
||||
<span class="badge bg-light text-dark ms-1">{{ stats.lots_without_equipment }}</span>
|
||||
</div>
|
||||
<div class="card-body py-2">
|
||||
{% if lots_without_equipment %}
|
||||
<div class="list-group list-group-flush">
|
||||
{% for lot in lots_without_equipment[:8] %}
|
||||
<a class="list-group-item list-group-item-action px-0 py-1 d-flex justify-content-between" href="{{ url_for('lots.detail', id=lot.id) }}">
|
||||
<span>{{ lot.name }}</span><i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if lots_without_equipment|length > 8 %}<small class="text-muted">{{ lots_without_equipment|length - 8 }} autre(s)</small>{% endif %}
|
||||
{% else %}
|
||||
<span class="text-success"><i class="bi bi-check-circle me-1"></i>Tous les lots présents ont un équipement.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 {% if stats.lots_missing_duration %}border-danger{% else %}border-success{% endif %}">
|
||||
<div class="card-header {% if stats.lots_missing_duration %}bg-danger text-white{% else %}bg-success text-white{% endif %} py-2">
|
||||
<i class="bi bi-stopwatch me-1"></i>Lots équipés avec durée manquante
|
||||
<span class="badge bg-light text-dark ms-1">{{ stats.lots_missing_duration }}</span>
|
||||
</div>
|
||||
<div class="card-body py-2">
|
||||
{% if lots_missing_duration %}
|
||||
<div class="list-group list-group-flush">
|
||||
{% for item in lots_missing_duration[:8] %}
|
||||
<div class="list-group-item px-0 py-1">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="{{ url_for('lots.detail', id=item.lot.id) }}"><strong>{{ item.lot.name }}</strong></a>
|
||||
{% if item.no_task %}<span class="badge bg-danger">Aucune tâche</span>{% else %}<span class="badge bg-danger">{{ item.tasks|length }} tâche(s)</span>{% endif %}
|
||||
</div>
|
||||
{% if item.no_task %}
|
||||
<a class="small text-decoration-none d-block ms-2" href="{{ url_for('lots.detail', id=item.lot.id) }}">
|
||||
<i class="bi bi-plus-circle me-1"></i>Ajouter une tâche et définir sa durée
|
||||
</a>
|
||||
{% endif %}
|
||||
{% for task in item.tasks[:3] %}
|
||||
<a class="small text-decoration-none d-block ms-2" href="{{ url_for('lots.task_edit', lot_id=item.lot.id, task_id=task.id) }}">
|
||||
<i class="bi bi-pencil me-1"></i>{{ task.tache or 'Tâche sans nom' }} — définir la durée
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="text-success"><i class="bi bi-check-circle me-1"></i>Toutes les tâches actives ont une durée définie.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alertes -->
|
||||
{% if alerts %}
|
||||
<div class="card border-warning mb-3">
|
||||
|
|
@ -652,4 +712,4 @@ fetch('/scheduler/api/overdue-count').then(r => r.json()).then(d => {
|
|||
}
|
||||
}).catch(() => {});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue