feat: add mailing and bilan to send

This commit is contained in:
2025-09-10 09:04:32 +02:00
parent 2c549c7234
commit 844d4d6bba
14 changed files with 2334 additions and 3 deletions

View File

@@ -438,4 +438,170 @@ def delete(id):
db.session.commit()
current_app.logger.info(f'Évaluation supprimée: {title} (ID: {id})')
flash('Évaluation supprimée avec succès !', 'success')
return redirect(url_for('assessments.list'))
return redirect(url_for('assessments.list'))
@bp.route('/<int:id>/preview-report/<int:student_id>')
@handle_db_errors
def preview_report(id, student_id):
"""Prévisualise le bilan d'un élève dans le navigateur."""
from services.student_report_service import StudentReportService
from models import Student
# Récupérer l'évaluation
assessment_repo = AssessmentRepository()
assessment = assessment_repo.get_with_full_details_or_404(id)
# Récupérer l'élève
student = Student.query.get_or_404(student_id)
# Générer le rapport
report_service = StudentReportService()
report_data = report_service.generate_student_report(assessment, student)
# Afficher le template email directement
return render_template('email/student_report.html', report=report_data)
@bp.route('/<int:id>/send-reports', methods=['POST'])
@handle_db_errors
def send_reports(id):
"""Envoie les bilans d'évaluation par email."""
try:
# Récupération des données du formulaire
data = request.get_json()
if not data:
return jsonify({'success': False, 'error': 'Aucune donnée fournie'}), 400
student_ids = data.get('student_ids', [])
custom_message = data.get('custom_message', '').strip()
if not student_ids:
return jsonify({'success': False, 'error': 'Aucun élève sélectionné'}), 400
# Récupération de l'évaluation
assessment_repo = AssessmentRepository()
assessment = assessment_repo.get_with_full_details_or_404(id)
# Vérification de la configuration email
from services.email_service import EmailService
from services.student_report_service import StudentReportService
from flask import render_template
email_service = EmailService()
if not email_service.is_configured():
return jsonify({
'success': False,
'error': 'Configuration email incomplète. Rendez-vous dans Configuration > Email.'
}), 400
report_service = StudentReportService()
# Génération des rapports
reports_data = report_service.generate_multiple_reports(assessment, student_ids)
if reports_data['error_count'] > 0:
current_app.logger.warning(f"Erreurs lors de la génération de rapports: {reports_data['errors']}")
# Envoi des emails
sent_count = 0
errors = []
for student_id, report_data in reports_data['reports'].items():
try:
student = report_data['student']
# Vérification de l'email de l'élève
if not student['email']:
errors.append(f"{student['full_name']}: Aucune adresse email")
continue
# Validation de l'email
validation = email_service.validate_email_addresses([student['email']])
if validation['invalid_count'] > 0:
errors.append(f"{student['full_name']}: Adresse email invalide ({student['email']})")
continue
# Génération du HTML de l'email
html_content = render_template('email/student_report.html',
report=report_data,
custom_message=custom_message)
# Sujet de l'email
subject = f"Bilan d'évaluation - {assessment.title} - {student['full_name']}"
# Envoi de l'email
result = email_service.send_email([student['email']], subject, html_content)
if result['success']:
sent_count += 1
current_app.logger.info(f"Bilan envoyé à {student['full_name']} ({student['email']})")
else:
errors.append(f"{student['full_name']}: {result['error']}")
current_app.logger.error(f"Erreur envoi bilan à {student['full_name']}: {result['error']}")
except Exception as e:
error_msg = f"{report_data.get('student', {}).get('full_name', 'Élève inconnu')}: Erreur inattendue - {str(e)}"
errors.append(error_msg)
current_app.logger.error(f"Erreur envoi bilan: {e}")
# Préparer la réponse
response_data = {
'success': sent_count > 0,
'sent_count': sent_count,
'total_requested': len(student_ids),
'error_count': len(errors),
'errors': errors
}
if sent_count > 0:
if len(errors) == 0:
response_data['message'] = f"{sent_count} bilan(s) envoyé(s) avec succès !"
else:
response_data['message'] = f"{sent_count} bilan(s) envoyé(s), {len(errors)} erreur(s)"
else:
response_data['message'] = f"❌ Aucun bilan envoyé - {len(errors)} erreur(s)"
return jsonify(response_data)
except Exception as e:
current_app.logger.error(f"Erreur lors de l'envoi de bilans: {e}")
return jsonify({
'success': False,
'error': f'Erreur inattendue: {str(e)}'
}), 500
@bp.route('/<int:id>/eligible-students')
@handle_db_errors
def get_eligible_students(id):
"""Récupère la liste des élèves éligibles avec leurs emails pour l'envoi de bilans."""
try:
assessment_repo = AssessmentRepository()
assessment = assessment_repo.get_with_full_details_or_404(id)
# Récupérer les élèves éligibles (ceux qui étaient dans la classe à la date de l'évaluation)
eligible_students = []
for student in assessment.class_group.get_students_at_date(assessment.date):
eligible_students.append({
'id': student.id,
'first_name': student.first_name,
'last_name': student.last_name,
'full_name': student.full_name,
'email': student.email or '',
'has_email': bool(student.email)
})
# Trier par nom de famille puis prénom
eligible_students.sort(key=lambda x: (x['last_name'].lower(), x['first_name'].lower()))
return jsonify({
'success': True,
'students': eligible_students,
'total_count': len(eligible_students),
'with_email_count': len([s for s in eligible_students if s['has_email']])
})
except Exception as e:
current_app.logger.error(f"Erreur récupération élèves éligibles: {e}")
return jsonify({
'success': False,
'error': f'Erreur: {str(e)}'
}), 500

View File

@@ -446,6 +446,105 @@ def update_general():
return redirect(url_for('config.general'))
@bp.route('/email')
def email():
"""Page de configuration email."""
try:
# Récupérer la configuration email actuelle
email_config = {
'smtp_host': config_manager.get('email.smtp_host', ''),
'smtp_port': config_manager.get('email.smtp_port', '587'),
'username': config_manager.get('email.username', ''),
'password': config_manager.get('email.password', ''),
'use_tls': config_manager.get('email.use_tls', 'true') == 'true',
'from_name': config_manager.get('email.from_name', 'Notytex'),
'from_address': config_manager.get('email.from_address', ''),
}
return render_template('config/email.html', email_config=email_config)
except Exception as e:
return handle_error(e, "Erreur lors du chargement de la configuration email")
@bp.route('/email/update', methods=['POST'])
@handle_db_errors
def update_email():
"""Mettre à jour la configuration email."""
try:
# Récupérer les données du formulaire
smtp_host = request.form.get('smtp_host', '').strip()
smtp_port = request.form.get('smtp_port', '587').strip()
username = request.form.get('username', '').strip()
password = request.form.get('password', '').strip()
use_tls = request.form.get('use_tls') == 'on'
from_name = request.form.get('from_name', 'Notytex').strip()
from_address = request.form.get('from_address', '').strip()
# Validation des données
if smtp_host and not smtp_port.isdigit():
flash('Le port SMTP doit être un nombre', 'error')
return redirect(url_for('config.email'))
if from_address:
import re
email_regex = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
if not email_regex.match(from_address):
flash('Format d\'adresse email invalide', 'error')
return redirect(url_for('config.email'))
# Sauvegarder la configuration
config_manager.set('email.smtp_host', smtp_host)
config_manager.set('email.smtp_port', smtp_port)
config_manager.set('email.username', username)
config_manager.set('email.password', password)
config_manager.set('email.use_tls', 'true' if use_tls else 'false')
config_manager.set('email.from_name', from_name)
config_manager.set('email.from_address', from_address)
if config_manager.save():
flash('Configuration email mise à jour avec succès', 'success')
else:
flash('Erreur lors de la sauvegarde', 'error')
except Exception as e:
logging.error(f"Erreur mise à jour config email: {e}")
flash('Erreur lors de la mise à jour', 'error')
return redirect(url_for('config.email'))
@bp.route('/email/test', methods=['POST'])
@handle_db_errors
def test_email():
"""Tester la configuration email."""
try:
test_email_address = request.form.get('test_email', '').strip()
if not test_email_address:
flash('Adresse email de test requise', 'error')
return redirect(url_for('config.email'))
# Validation de l'adresse email
import re
email_regex = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
if not email_regex.match(test_email_address):
flash('Format d\'adresse email invalide', 'error')
return redirect(url_for('config.email'))
# Tenter l'envoi d'un email de test
from services.email_service import EmailService
email_service = EmailService(config_manager)
result = email_service.send_test_email(test_email_address)
if result['success']:
flash(f'Email de test envoyé avec succès à {test_email_address}', 'success')
else:
flash(f'Erreur lors de l\'envoi du test: {result["error"]}', 'error')
except Exception as e:
logging.error(f"Erreur test email: {e}")
flash('Erreur lors du test d\'envoi', 'error')
return redirect(url_for('config.email'))
@bp.route('/reset', methods=['POST'])
def reset_config():
"""Réinitialise la configuration aux valeurs par défaut."""