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.college import Building, Room
|
||||||
from ..models.company import Part
|
from ..models.company import Part
|
||||||
from app_new.constants import INTERVENTION_STATUSES, EQUIPMENT_STATUSES
|
from app_new.constants import INTERVENTION_STATUSES, EQUIPMENT_STATUSES
|
||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
dashboard_bp = Blueprint('dashboard', __name__)
|
dashboard_bp = Blueprint('dashboard', __name__)
|
||||||
|
|
||||||
|
|
@ -23,6 +24,7 @@ def index():
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from ..services.planning_service import PlanningService
|
from ..services.planning_service import PlanningService
|
||||||
from ..models.planning import ScheduledTask, AdminTask
|
from ..models.planning import ScheduledTask, AdminTask
|
||||||
|
from ..models.maintenance import Lot
|
||||||
|
|
||||||
# Statistiques
|
# Statistiques
|
||||||
stats = {
|
stats = {
|
||||||
|
|
@ -39,6 +41,41 @@ def index():
|
||||||
'parts_low_stock': Part.query.filter(Part.quantity <= Part.min_quantity).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
|
# 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()
|
||||||
|
|
||||||
|
|
@ -108,6 +145,8 @@ def index():
|
||||||
urgent_interventions=urgent_interventions,
|
urgent_interventions=urgent_interventions,
|
||||||
pending_interpretations=pending_interpretations,
|
pending_interpretations=pending_interpretations,
|
||||||
day_schedule=day_schedule,
|
day_schedule=day_schedule,
|
||||||
|
lots_without_equipment=lots_without_equipment,
|
||||||
|
lots_missing_duration=lots_missing_duration,
|
||||||
today=today,
|
today=today,
|
||||||
statuses=INTERVENTION_STATUSES)
|
statuses=INTERVENTION_STATUSES)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,66 @@
|
||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Alertes -->
|
||||||
{% if alerts %}
|
{% if alerts %}
|
||||||
<div class="card border-warning mb-3">
|
<div class="card border-warning mb-3">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue