Files
notytex/routes/student_movements.py

336 lines
14 KiB
Python

from flask import Blueprint, render_template, request, jsonify, flash, redirect, url_for, current_app
from datetime import date, datetime
from models import db, Student, ClassGroup, StudentEnrollment
from repositories.temporal_student_repository import TemporalStudentRepository
from utils import handle_db_errors
bp = Blueprint('student_movements', __name__, url_prefix='/movements')
@bp.route('/')
@handle_db_errors
def movements_index():
"""Page principale de gestion des mouvements d'élèves."""
temporal_repo = TemporalStudentRepository()
# Récupérer les mouvements récents (30 derniers jours)
from datetime import date, timedelta
recent_start = date.today() - timedelta(days=30)
recent_end = date.today()
recent_movements = temporal_repo.find_students_with_movements_in_period(recent_start, recent_end)
# Récupérer toutes les classes pour les formulaires
classes = ClassGroup.query.order_by(ClassGroup.name).all()
# Récupérer tous les élèves pour le formulaire d'inscription
all_students = Student.query.order_by(Student.last_name, Student.first_name).all()
return render_template('student_movements.html',
recent_movements=recent_movements,
classes=classes,
all_students=all_students)
@bp.route('/enroll', methods=['POST'])
@handle_db_errors
def enroll_student():
"""Inscrire un élève dans une classe."""
temporal_repo = TemporalStudentRepository()
try:
data = request.get_json() if request.is_json else request.form
student_id = int(data.get('student_id'))
class_group_id = int(data.get('class_group_id'))
enrollment_date_str = data.get('enrollment_date', str(date.today()))
enrollment_reason = data.get('enrollment_reason', '')
# Validation de la date
enrollment_date = datetime.strptime(enrollment_date_str, '%Y-%m-%d').date()
# Vérifier que l'élève et la classe existent
student = Student.query.get_or_404(student_id)
class_group = ClassGroup.query.get_or_404(class_group_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
})
else:
flash(f'Élève {student.full_name} inscrit en {class_group.name}', 'success')
return redirect(url_for('student_movements.movements_index'))
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(url_for('student_movements.movements_index'))
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(url_for('student_movements.movements_index'))
@bp.route('/departure', methods=['POST'])
@handle_db_errors
def student_departure():
"""Enregistrer le départ d'un élève."""
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')
return redirect(url_for('student_movements.movements_index'))
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(url_for('student_movements.movements_index'))
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(url_for('student_movements.movements_index'))
@bp.route('/transfer', methods=['POST'])
@handle_db_errors
def transfer_student():
"""Transférer un élève d'une classe à une autre."""
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_group_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')
return redirect(url_for('student_movements.movements_index'))
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(url_for('student_movements.movements_index'))
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(url_for('student_movements.movements_index'))
@bp.route('/history/<int:student_id>')
@handle_db_errors
def student_history(student_id):
"""Afficher l'historique des inscriptions d'un élève."""
temporal_repo = TemporalStudentRepository()
student = Student.query.get_or_404(student_id)
enrollment_history = temporal_repo.get_enrollment_history(student_id)
if request.is_json:
history_data = []
for enrollment in enrollment_history:
history_data.append({
'id': enrollment.id,
'class_name': enrollment.class_group.name,
'enrollment_date': enrollment.enrollment_date.isoformat(),
'departure_date': enrollment.departure_date.isoformat() if enrollment.departure_date else None,
'enrollment_reason': enrollment.enrollment_reason,
'departure_reason': enrollment.departure_reason,
'is_active': enrollment.is_active
})
return jsonify({
'student': {
'id': student.id,
'name': student.full_name
},
'history': history_data
})
else:
return render_template('student_history.html',
student=student,
enrollment_history=enrollment_history)
@bp.route('/api/eligible-students/<int:assessment_id>')
@handle_db_errors
def api_eligible_students(assessment_id):
"""API pour récupérer les élèves éligibles pour une évaluation."""
from models import Assessment
temporal_repo = TemporalStudentRepository()
assessment = Assessment.query.get_or_404(assessment_id)
eligible_students = temporal_repo.find_eligible_for_assessment(assessment)
current_students = temporal_repo.find_current_students_in_class(assessment.class_group_id)
eligible_data = []
for student in eligible_students:
eligible_data.append({
'id': student.id,
'name': student.full_name,
'email': student.email,
'eligible': True
})
# Ajouter les élèves non-éligibles pour comparaison
ineligible_students = [s for s in current_students if s not in eligible_students]
for student in ineligible_students:
eligible_data.append({
'id': student.id,
'name': student.full_name,
'email': student.email,
'eligible': False
})
return jsonify({
'assessment': {
'id': assessment.id,
'title': assessment.title,
'date': assessment.date.isoformat() if assessment.date else None
},
'total_eligible': len(eligible_students),
'total_current': len(current_students),
'students': eligible_data
})
@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('student_movements.movements_index'))
# 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('student_movements.movements_index'))
# 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 (page élèves de classe si disponible)
return redirect(request.referrer or url_for('student_movements.movements_index'))
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('student_movements.movements_index'))
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('student_movements.movements_index'))