Files
notytex/routes/classes.py

796 lines
34 KiB
Python

from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, current_app, abort
from models import db, ClassGroup, Student, Assessment, StudentEnrollment
from forms import ClassGroupForm
from utils import handle_db_errors, ValidationError
from repositories.class_repository import ClassRepository
from datetime import date, datetime
bp = Blueprint('classes', __name__, url_prefix='/classes')
@bp.route('/new')
@handle_db_errors
def new():
"""Formulaire de création d'une nouvelle classe."""
form = ClassGroupForm()
return render_template('class_form.html',
form=form,
title="Créer une nouvelle classe",
is_edit=False)
@bp.route('/', methods=['POST'])
@handle_db_errors
def create():
"""Traitement de la création d'une classe."""
form = ClassGroupForm()
class_repo = ClassRepository()
if form.validate_on_submit():
try:
# Vérification d'unicité du nom de classe
if class_repo.exists_by_name(form.name.data):
flash('Une classe avec ce nom existe déjà.', 'error')
return render_template('class_form.html',
form=form,
title="Créer une nouvelle classe",
is_edit=False)
# Création de la nouvelle classe
class_group = ClassGroup(
name=form.name.data,
description=form.description.data,
year=form.year.data
)
db.session.add(class_group)
db.session.commit()
current_app.logger.info(f'Nouvelle classe créée: {class_group.name}')
flash(f'Classe "{class_group.name}" créée avec succès !', 'success')
return redirect(url_for('classes'))
except Exception as e:
db.session.rollback()
current_app.logger.error(f'Erreur lors de la création de la classe: {e}')
flash('Erreur lors de la création de la classe.', 'error')
return render_template('class_form.html',
form=form,
title="Créer une nouvelle classe",
is_edit=False)
@bp.route('/<int:id>/edit')
@handle_db_errors
def edit(id):
"""Formulaire de modification d'une classe."""
class_repo = ClassRepository()
class_group = class_repo.get_or_404(id)
form = ClassGroupForm(obj=class_group)
return render_template('class_form.html',
form=form,
class_group=class_group,
title=f"Modifier la classe {class_group.name}",
is_edit=True)
@bp.route('/<int:id>', methods=['POST'])
@handle_db_errors
def update(id):
"""Traitement de la modification d'une classe."""
class_repo = ClassRepository()
class_group = class_repo.get_or_404(id)
form = ClassGroupForm()
if form.validate_on_submit():
try:
# Vérification d'unicité du nom (sauf si c'est le même nom)
if class_repo.exists_by_name(form.name.data, exclude_id=id):
flash('Une autre classe avec ce nom existe déjà.', 'error')
return render_template('class_form.html',
form=form,
class_group=class_group,
title=f"Modifier la classe {class_group.name}",
is_edit=True)
# Mise à jour des données
class_group.name = form.name.data
class_group.description = form.description.data
class_group.year = form.year.data
db.session.commit()
current_app.logger.info(f'Classe modifiée: {class_group.name}')
flash(f'Classe "{class_group.name}" modifiée avec succès !', 'success')
return redirect(url_for('classes'))
except Exception as e:
db.session.rollback()
current_app.logger.error(f'Erreur lors de la modification de la classe: {e}')
flash('Erreur lors de la modification de la classe.', 'error')
return render_template('class_form.html',
form=form,
class_group=class_group,
title=f"Modifier la classe {class_group.name}",
is_edit=True)
@bp.route('/<int:id>/delete', methods=['POST'])
@handle_db_errors
def delete(id):
"""Suppression d'une classe avec vérifications."""
class_repo = ClassRepository()
class_group = class_repo.get_or_404(id)
try:
# Vérifier s'il y a des étudiants ou des évaluations liés
can_delete, dependencies = class_repo.can_be_deleted(id)
if not can_delete:
students_count = dependencies['students']
assessments_count = dependencies['assessments']
flash(
f'Impossible de supprimer la classe "{class_group.name}". '
f'Elle contient {students_count} élève(s) et {assessments_count} évaluation(s). '
f'Supprimez d\'abord ces éléments.',
'error'
)
return redirect(url_for('classes'))
# Suppression de la classe
db.session.delete(class_group)
db.session.commit()
current_app.logger.info(f'Classe supprimée: {class_group.name}')
flash(f'Classe "{class_group.name}" supprimée avec succès.', 'success')
except Exception as e:
db.session.rollback()
current_app.logger.error(f'Erreur lors de la suppression de la classe: {e}')
flash('Erreur lors de la suppression de la classe.', 'error')
return redirect(url_for('classes'))
@bp.route('/<int:id>', methods=['GET'])
@handle_db_errors
def details(id):
"""Redirection transparente vers le dashboard de classe."""
return redirect(url_for('classes.dashboard', id=id))
@bp.route('/<int:id>/dashboard')
@handle_db_errors
def dashboard(id):
"""Page de présentation de classe avec statistiques."""
# Récupération paramètre trimestre
trimester = request.args.get('trimestre', type=int)
if trimester and trimester not in [1, 2, 3]:
trimester = None
# Repository optimisé
class_repo = ClassRepository()
class_group = class_repo.find_with_statistics(id, trimester)
if not class_group:
abort(404)
# Récupérer aussi les statistiques pour injection dans le template
stats_data = {}
try:
# Construction des données comme dans l'API
quantity_stats = class_group.get_trimester_statistics(trimester)
domain_analysis = class_group.get_domain_analysis(trimester)
competence_analysis = class_group.get_competence_analysis(trimester)
class_results = class_group.get_class_results(trimester)
stats_data = {
'summary': quantity_stats,
'domains': domain_analysis,
'competences': competence_analysis,
'results': class_results
}
except Exception as e:
current_app.logger.warning(f'Erreur lors du chargement des stats dashboard: {e}')
current_app.logger.debug(f'Dashboard classe {id} affiché pour trimestre {trimester}')
return render_template('class_dashboard.html',
class_group=class_group,
selected_trimester=trimester,
stats_data=stats_data)
@bp.route('/<int:id>/stats')
@handle_db_errors
def get_stats_api(id):
"""API JSON pour statistiques dynamiques (AJAX)."""
# Récupération paramètre trimestre
trimester = request.args.get('trimestre', type=int)
if trimester and trimester not in [1, 2, 3]:
trimester = None
# Repository optimisé
class_repo = ClassRepository()
class_group = class_repo.find_with_statistics(id, trimester)
if not class_group:
abort(404)
try:
# Construction de la réponse JSON avec les nouvelles méthodes du modèle
quantity_stats = class_group.get_trimester_statistics(trimester)
domain_analysis = class_group.get_domain_analysis(trimester)
competence_analysis = class_group.get_competence_analysis(trimester)
class_results = class_group.get_class_results(trimester)
# Compter le nombre d'évaluations selon le trimestre
if hasattr(class_group, '_filtered_assessments'):
assessments_count = len(class_group._filtered_assessments)
current_app.logger.debug(f'Assessments count from _filtered_assessments: {assessments_count}')
else:
# Fallback si _filtered_assessments n'existe pas
if trimester:
assessments_count = len([a for a in class_group.assessments if a.trimester == trimester])
else:
assessments_count = len(class_group.assessments)
current_app.logger.debug(f'Assessments count from fallback: {assessments_count}')
current_app.logger.debug(f'Final assessments_count value: {assessments_count}, type: {type(assessments_count)}')
stats = {
"quantity": {
"total": quantity_stats["total"],
"completed": quantity_stats["completed"],
"in_progress": quantity_stats["in_progress"],
"not_started": quantity_stats["not_started"]
},
"domains": domain_analysis["domains"], # Extraire directement le tableau
"competences": competence_analysis["competences"], # Extraire directement le tableau
"results": {
"average": class_results["overall_statistics"]["mean"],
"min": class_results["overall_statistics"]["min"],
"max": class_results["overall_statistics"]["max"],
"median": class_results["overall_statistics"]["median"],
"std_dev": class_results["overall_statistics"]["std_dev"],
"assessments_count": assessments_count,
"student_averages": class_results["student_averages"],
"student_averages_distribution": class_results["student_averages_distribution"]
}
}
current_app.logger.debug(f'Statistiques API générées pour classe {id}, trimestre {trimester}')
return jsonify(stats)
except Exception as e:
current_app.logger.error(f'Erreur génération statistiques API classe {id}: {e}')
return jsonify({"error": "Erreur lors de la génération des statistiques"}), 500
@bp.route('/<int:id>/details')
@handle_db_errors
def details_legacy(id):
"""Page de détail d'une classe avec ses étudiants et évaluations (legacy)."""
class_repo = ClassRepository()
class_group = class_repo.find_with_full_details(id)
if not class_group:
abort(404)
# Trier les étudiants par nom (optimisé en Python car déjà chargés)
students = sorted(class_group.students, key=lambda s: (s.last_name, s.first_name))
# Prendre les 5 évaluations les plus récentes (optimisé en Python car déjà chargées)
recent_assessments = sorted(class_group.assessments, key=lambda a: a.date, reverse=True)[:5]
return render_template('class_details.html',
class_group=class_group,
students=students,
recent_assessments=recent_assessments)
@bp.route('/<int:id>/council')
@handle_db_errors
def council_preparation(id):
"""Page de préparation du conseil de classe."""
# Le trimestre est obligatoire pour la préparation du conseil
trimester = request.args.get('trimestre', type=int)
if not trimester or trimester not in [1, 2, 3]:
flash('Veuillez sélectionner un trimestre pour préparer le conseil de classe.', 'error')
return redirect(url_for('classes.dashboard', id=id))
# Vérifier que la classe existe
class_repo = ClassRepository()
class_group = class_repo.get_or_404(id)
try:
# Injection de dépendances via factory
from services.council_services import CouncilServiceFactory
council_service = CouncilServiceFactory.create_council_preparation_service()
# Préparer toutes les données du conseil
council_data = council_service.prepare_council_data(id, trimester)
current_app.logger.info(f'Préparation conseil classe {id}, trimestre {trimester} - {len(council_data.student_summaries)} élèves')
return render_template('class_council_preparation.html',
class_group=class_group,
trimester=trimester,
council_data=council_data,
student_summaries=council_data.student_summaries,
class_statistics=council_data.class_statistics,
appreciation_stats=council_data.appreciation_stats)
except Exception as e:
current_app.logger.error(f'Erreur préparation conseil classe {id}: {e}')
flash('Erreur lors de la préparation des données du conseil de classe.', 'error')
return redirect(url_for('classes.dashboard', id=id))
@bp.route('/<int:class_id>/council/appreciation/<int:student_id>', methods=['POST'])
@handle_db_errors
def save_appreciation_api(class_id, student_id):
"""API pour sauvegarde d'appréciations (AJAX)."""
try:
# Vérifications de base
class_repo = ClassRepository()
class_group = class_repo.get_or_404(class_id)
data = request.get_json()
if not data:
return jsonify({'success': False, 'error': 'Données manquantes'}), 400
# Validation du trimestre
trimester = data.get('trimester')
if not trimester or trimester not in [1, 2, 3]:
return jsonify({'success': False, 'error': 'Trimestre invalide'}), 400
# Vérifier que l'élève appartient à cette classe
from models import Student
student = Student.query.get_or_404(student_id)
current_class = student.get_current_class()
if not current_class or current_class.id != class_id:
return jsonify({'success': False, 'error': 'Élève non trouvé dans cette classe'}), 403
# Préparer les données d'appréciation
appreciation_data = {
'student_id': student_id,
'class_group_id': class_id,
'trimester': trimester,
'general_appreciation': data.get('appreciation', '').strip() or None,
'strengths': data.get('strengths', '').strip() or None,
'areas_for_improvement': data.get('areas_for_improvement', '').strip() or None,
'status': data.get('status', 'draft')
}
# Sauvegarder via le service
from services.council_services import CouncilServiceFactory
appreciation_service = CouncilServiceFactory.create_appreciation_service()
result = appreciation_service.save_appreciation(appreciation_data)
current_app.logger.info(f'Appréciation sauvegardée - Élève {student_id}, Classe {class_id}, T{trimester}')
return jsonify({
'success': True,
'appreciation_id': result.id,
'last_modified': result.last_modified.isoformat(),
'status': result.status,
'has_content': result.has_content
})
except Exception as e:
current_app.logger.error(f'Erreur sauvegarde appréciation élève {student_id}: {e}')
return jsonify({
'success': False,
'error': 'Erreur lors de la sauvegarde'
}), 500
@bp.route('/<int:class_id>/council/api')
@handle_db_errors
def council_data_api(class_id):
"""API JSON pour récupérer les données d'un trimestre (AJAX)."""
try:
# Vérifications
trimester = request.args.get('trimestre', type=int)
if not trimester or trimester not in [1, 2, 3]:
return jsonify({'error': 'Trimestre invalide'}), 400
class_repo = ClassRepository()
class_group = class_repo.get_or_404(class_id)
# Récupérer les données via le service
from services.council_services import CouncilServiceFactory
council_service = CouncilServiceFactory.create_council_preparation_service()
council_data = council_service.prepare_council_data(class_id, trimester)
# Formatter pour JSON
response_data = {
'trimester': trimester,
'class_id': class_id,
'total_students': council_data.total_students,
'completed_appreciations': council_data.completed_appreciations,
'class_statistics': council_data.class_statistics,
'appreciation_stats': council_data.appreciation_stats,
'students': []
}
# Ajouter les données des élèves
for summary in council_data.student_summaries:
student_data = {
'id': summary.student.id,
'name': summary.student.full_name,
'last_name': summary.student.last_name,
'first_name': summary.student.first_name,
'average': summary.overall_average,
'assessment_count': summary.assessment_count,
'performance_status': summary.performance_status,
'has_appreciation': summary.has_appreciation,
'assessments': {}
}
# Ajouter les détails des évaluations
for assessment_id, assessment_data in summary.grades_by_assessment.items():
student_data['assessments'][assessment_id] = {
'score': assessment_data['score'],
'max': assessment_data['max'],
'title': assessment_data['title']
}
response_data['students'].append(student_data)
return jsonify(response_data)
except Exception as e:
current_app.logger.error(f'Erreur API données conseil classe {class_id}: {e}')
return jsonify({'error': 'Erreur lors de la récupération des données'}), 500
@bp.route('/<int:id>/students')
@handle_db_errors
def students(id):
"""Page de gestion des élèves d'une classe."""
class_repo = ClassRepository()
class_group = class_repo.get_or_404(id)
try:
# Repository temporel pour les élèves
from repositories.temporal_student_repository import TemporalStudentRepository
temporal_repo = TemporalStudentRepository()
# Élèves actuellement inscrits
current_students = temporal_repo.find_current_students_in_class(id)
# Historique des mouvements (derniers 6 mois)
from datetime import date, timedelta
six_months_ago = date.today() - timedelta(days=180)
movements = temporal_repo.find_students_with_movements_in_period(six_months_ago, date.today())
# Filtrer les mouvements de cette classe
class_movements = []
for student, enrollments in movements:
class_enrollments = [e for e in enrollments if e.class_group_id == id]
if class_enrollments:
class_movements.append((student, class_enrollments))
# Statistiques d'effectifs
total_current = len(current_students)
# Compter les arrivées et départs récents (30 derniers jours)
thirty_days_ago = date.today() - timedelta(days=30)
recent_arrivals = 0
recent_departures = 0
for student, enrollments in class_movements:
for enrollment in enrollments:
if enrollment.enrollment_date and enrollment.enrollment_date >= thirty_days_ago:
recent_arrivals += 1
if enrollment.departure_date and enrollment.departure_date >= thirty_days_ago:
recent_departures += 1
# Toutes les classes pour les transferts
all_classes = ClassRepository().find_all_ordered('name')
other_classes = [c for c in all_classes if c.id != id]
# Élèves non inscrits dans cette classe pour inscription
from sqlalchemy import func
all_students = Student.query.order_by(func.lower(Student.last_name), func.lower(Student.first_name)).all()
available_students = []
for student in all_students:
current_class = student.get_current_class()
if not current_class or current_class.id != id:
available_students.append(student)
current_app.logger.debug(f'Page élèves classe {id} - {total_current} élèves actuels, {len(class_movements)} mouvements')
return render_template('class_students.html',
class_group=class_group,
current_students=current_students,
class_movements=class_movements,
other_classes=other_classes,
available_students=available_students,
stats={
'total_current': total_current,
'recent_arrivals': recent_arrivals,
'recent_departures': recent_departures
})
except Exception as e:
current_app.logger.error(f'Erreur page élèves classe {id}: {e}')
flash('Erreur lors du chargement des données des élèves.', 'error')
return redirect(url_for('classes.dashboard', id=id))
@bp.route('/enroll', methods=['POST'])
@handle_db_errors
def enroll_student():
"""Inscrire un élève dans une classe (existant ou nouveau)."""
from repositories.temporal_student_repository import TemporalStudentRepository
temporal_repo = TemporalStudentRepository()
try:
data = request.get_json() if request.is_json else request.form
class_group_id = int(data.get('class_id'))
enrollment_date_str = data.get('enrollment_date', str(date.today()))
enrollment_reason = data.get('enrollment_reason', '')
mode = data.get('mode', 'existing')
# Validation de la date
enrollment_date = datetime.strptime(enrollment_date_str, '%Y-%m-%d').date()
# Vérifier que la classe existe
class_group = ClassGroup.query.get_or_404(class_group_id)
if mode == 'new':
# Mode création d'un nouvel élève
first_name = data.get('new_first_name', '').strip()
last_name = data.get('new_last_name', '').strip()
email = data.get('new_email', '').strip() or None
if not first_name or not last_name:
raise ValueError("Le prénom et le nom sont obligatoires pour un nouvel élève")
# Vérifier que l'email n'est pas déjà utilisé si fourni
if email:
existing_email = Student.query.filter_by(email=email).first()
if existing_email:
raise ValueError("Un élève avec cet email existe déjà")
# Créer le nouvel élève
student = Student(
first_name=first_name,
last_name=last_name,
email=email
)
db.session.add(student)
db.session.flush() # Pour obtenir l'ID du nouvel élève
current_app.logger.info(f'Nouvel élève créé: {student.full_name}')
else:
# Mode élève existant
student_id = data.get('student_id')
if not student_id:
raise ValueError("Veuillez sélectionner un élève")
student_id = int(student_id)
student = Student.query.get_or_404(student_id)
# Créer l'inscription
enrollment = temporal_repo.create_enrollment(
student.id, class_group_id, enrollment_date, enrollment_reason
)
db.session.commit()
current_app.logger.info(f'Inscription créée: Élève {student.full_name} en {class_group.name}')
if request.is_json:
return jsonify({
'success': True,
'message': f'Élève {student.full_name} inscrit en {class_group.name}',
'enrollment_id': enrollment.id,
'student_id': student.id,
'is_new_student': mode == 'new'
})
else:
if mode == 'new':
flash(f'Nouvel élève {student.full_name} créé et inscrit en {class_group.name}', 'success')
else:
flash(f'Élève {student.full_name} inscrit en {class_group.name}', 'success')
# Pour une mise à jour immédiate de la liste, utiliser JavaScript pour recharger
return redirect(url_for('classes.students', id=class_group_id) + '?reload=1')
except ValueError as e:
error_msg = str(e)
current_app.logger.warning(f'Erreur inscription élève: {error_msg}')
if request.is_json:
return jsonify({'success': False, 'error': error_msg}), 400
else:
flash(error_msg, 'error')
return redirect(request.referrer or url_for('classes'))
except Exception as e:
db.session.rollback()
current_app.logger.error(f'Erreur inscription élève: {e}')
if request.is_json:
return jsonify({'success': False, 'error': 'Erreur lors de l\'inscription'}), 500
else:
flash('Erreur lors de l\'inscription', 'error')
return redirect(request.referrer or url_for('classes'))
@bp.route('/transfer', methods=['POST'])
@handle_db_errors
def transfer_student():
"""Transférer un élève d'une classe à une autre."""
from repositories.temporal_student_repository import TemporalStudentRepository
temporal_repo = TemporalStudentRepository()
try:
data = request.get_json() if request.is_json else request.form
student_id = int(data.get('student_id'))
new_class_group_id = int(data.get('new_class_id'))
transfer_date_str = data.get('transfer_date', str(date.today()))
transfer_reason = data.get('transfer_reason', '')
# Validation de la date
transfer_date = datetime.strptime(transfer_date_str, '%Y-%m-%d').date()
# Vérifier que l'élève et les classes existent
student = Student.query.get_or_404(student_id)
new_class_group = ClassGroup.query.get_or_404(new_class_group_id)
# Effectuer le transfert
old_enrollment, new_enrollment = temporal_repo.transfer_student(
student_id, new_class_group_id, transfer_date, transfer_reason
)
db.session.commit()
current_app.logger.info(f'Transfert effectué: Élève {student.full_name} vers {new_class_group.name}')
if request.is_json:
return jsonify({
'success': True,
'message': f'Élève {student.full_name} transféré vers {new_class_group.name}',
'old_enrollment_id': old_enrollment.id,
'new_enrollment_id': new_enrollment.id
})
else:
flash(f'Élève {student.full_name} transféré vers {new_class_group.name}', 'success')
# Retourner à la page d'origine avec rechargement
referrer = request.referrer
if referrer and 'classes/' in referrer and '/students' in referrer:
return redirect(referrer + ('&' if '?' in referrer else '?') + 'reload=1')
return redirect(request.referrer or url_for('classes'))
except ValueError as e:
error_msg = str(e)
current_app.logger.warning(f'Erreur transfert élève: {error_msg}')
if request.is_json:
return jsonify({'success': False, 'error': error_msg}), 400
else:
flash(error_msg, 'error')
return redirect(request.referrer or url_for('classes'))
except Exception as e:
db.session.rollback()
current_app.logger.error(f'Erreur transfert élève: {e}')
if request.is_json:
return jsonify({'success': False, 'error': 'Erreur lors du transfert'}), 500
else:
flash('Erreur lors du transfert', 'error')
return redirect(request.referrer or url_for('classes'))
@bp.route('/departure', methods=['POST'])
@handle_db_errors
def student_departure():
"""Enregistrer le départ d'un élève."""
from repositories.temporal_student_repository import TemporalStudentRepository
temporal_repo = TemporalStudentRepository()
try:
data = request.get_json() if request.is_json else request.form
student_id = int(data.get('student_id'))
departure_date_str = data.get('departure_date', str(date.today()))
departure_reason = data.get('departure_reason', '')
# Validation de la date
departure_date = datetime.strptime(departure_date_str, '%Y-%m-%d').date()
# Vérifier que l'élève existe
student = Student.query.get_or_404(student_id)
# Terminer l'inscription active
enrollment = temporal_repo.end_enrollment(
student_id, departure_date, departure_reason
)
if not enrollment:
raise ValueError("Aucune inscription active trouvée pour cet élève")
db.session.commit()
current_app.logger.info(f'Départ enregistré: Élève {student.full_name}')
if request.is_json:
return jsonify({
'success': True,
'message': f'Départ de {student.full_name} enregistré',
'enrollment_id': enrollment.id
})
else:
flash(f'Départ de {student.full_name} enregistré', 'success')
# Retourner à la page d'origine avec rechargement
referrer = request.referrer
if referrer and 'classes/' in referrer and '/students' in referrer:
return redirect(referrer + ('&' if '?' in referrer else '?') + 'reload=1')
return redirect(request.referrer or url_for('classes'))
except ValueError as e:
error_msg = str(e)
current_app.logger.warning(f'Erreur départ élève: {error_msg}')
if request.is_json:
return jsonify({'success': False, 'error': error_msg}), 400
else:
flash(error_msg, 'error')
return redirect(request.referrer or url_for('classes'))
except Exception as e:
db.session.rollback()
current_app.logger.error(f'Erreur départ élève: {e}')
if request.is_json:
return jsonify({'success': False, 'error': 'Erreur lors de l\'enregistrement du départ'}), 500
else:
flash('Erreur lors de l\'enregistrement du départ', 'error')
return redirect(request.referrer or url_for('classes'))
@bp.route('/cancel-departure', methods=['POST'])
@handle_db_errors
def cancel_departure():
"""Annuler le départ d'un élève (remettre inscription active)."""
try:
data = request.get_json() if request.is_json else request.form
enrollment_id = int(data.get('enrollment_id'))
# Récupérer l'inscription
enrollment = StudentEnrollment.query.get_or_404(enrollment_id)
# Vérifier que l'inscription a bien une date de départ
if not enrollment.departure_date:
flash('Cette inscription n\'a pas de date de départ à annuler', 'error')
return redirect(request.referrer or url_for('classes'))
# Vérifier qu'il n'y a pas déjà une inscription active pour cet élève
active_enrollment = StudentEnrollment.query.filter_by(
student_id=enrollment.student_id,
departure_date=None
).first()
if active_enrollment:
flash(f'L\'élève {enrollment.student.first_name} {enrollment.student.last_name} est déjà inscrit dans {active_enrollment.class_group.name}', 'error')
return redirect(request.referrer or url_for('classes'))
# Annuler le départ en supprimant la date de départ
old_departure_date = enrollment.departure_date
enrollment.departure_date = None
enrollment.departure_reason = None
db.session.commit()
current_app.logger.info(f'Départ annulé - Élève {enrollment.student_id} réintégré en {enrollment.class_group.name}')
flash(f'Départ de {enrollment.student.first_name} {enrollment.student.last_name} annulé. Élève réintégré en {enrollment.class_group.name}', 'success')
# Rediriger vers la page d'origine avec rechargement
referrer = request.referrer
if referrer and 'classes/' in referrer and '/students' in referrer:
return redirect(referrer + ('&' if '?' in referrer else '?') + 'reload=1')
return redirect(request.referrer or url_for('classes'))
except (ValueError, TypeError) as e:
current_app.logger.error(f'Erreur données annulation départ: {e}')
flash('Données d\'annulation invalides', 'error')
return redirect(request.referrer or url_for('classes'))
except Exception as e:
db.session.rollback()
current_app.logger.error(f'Erreur annulation départ: {e}')
flash('Erreur lors de l\'annulation du départ', 'error')
return redirect(request.referrer or url_for('classes'))