feat: add concil page

This commit is contained in:
2025-08-11 06:01:23 +02:00
parent 13f0e69bb0
commit c132419213
17 changed files with 5072 additions and 12 deletions

View File

@@ -0,0 +1,170 @@
"""
Repository pour la gestion des appréciations du conseil de classe.
"""
from typing import List, Optional
from sqlalchemy.orm import joinedload
from models import CouncilAppreciation, db
from repositories.base_repository import BaseRepository
class AppreciationRepository(BaseRepository[CouncilAppreciation]):
"""Repository pour les appréciations du conseil de classe."""
def __init__(self):
super().__init__(CouncilAppreciation)
def find_by_class_trimester(self, class_group_id: int, trimester: int) -> List[CouncilAppreciation]:
"""Trouve toutes les appréciations d'une classe pour un trimestre."""
return CouncilAppreciation.query.filter_by(
class_group_id=class_group_id,
trimester=trimester
).options(
joinedload(CouncilAppreciation.student),
joinedload(CouncilAppreciation.class_group)
).all()
def find_by_student_trimester(
self,
student_id: int,
class_group_id: int,
trimester: int
) -> Optional[CouncilAppreciation]:
"""Trouve l'appréciation d'un élève pour un trimestre."""
return CouncilAppreciation.query.filter_by(
student_id=student_id,
class_group_id=class_group_id,
trimester=trimester
).options(
joinedload(CouncilAppreciation.student),
joinedload(CouncilAppreciation.class_group)
).first()
def find_by_student_all_trimesters(
self,
student_id: int,
class_group_id: int
) -> List[CouncilAppreciation]:
"""Trouve toutes les appréciations d'un élève pour tous les trimestres."""
return CouncilAppreciation.query.filter_by(
student_id=student_id,
class_group_id=class_group_id
).options(
joinedload(CouncilAppreciation.student),
joinedload(CouncilAppreciation.class_group)
).order_by(CouncilAppreciation.trimester).all()
def count_with_content_by_class_trimester(
self,
class_group_id: int,
trimester: int
) -> int:
"""Compte le nombre d'appréciations avec contenu pour une classe/trimestre."""
return CouncilAppreciation.query.filter(
CouncilAppreciation.class_group_id == class_group_id,
CouncilAppreciation.trimester == trimester,
db.or_(
CouncilAppreciation.general_appreciation.isnot(None),
CouncilAppreciation.strengths.isnot(None),
CouncilAppreciation.areas_for_improvement.isnot(None)
)
).count()
def get_completion_stats(self, class_group_id: int, trimester: int) -> dict:
"""Statistiques de completion des appréciations pour une classe/trimestre."""
from models import Student
# Nombre total d'élèves dans la classe
total_students = Student.query.filter_by(class_group_id=class_group_id).count()
# Nombre d'appréciations existantes
total_appreciations = CouncilAppreciation.query.filter_by(
class_group_id=class_group_id,
trimester=trimester
).count()
# Nombre d'appréciations avec contenu
completed_appreciations = self.count_with_content_by_class_trimester(
class_group_id, trimester
)
# Nombre d'appréciations finalisées
finalized_appreciations = CouncilAppreciation.query.filter_by(
class_group_id=class_group_id,
trimester=trimester,
status='finalized'
).count()
return {
'total_students': total_students,
'total_appreciations': total_appreciations,
'completed_appreciations': completed_appreciations,
'finalized_appreciations': finalized_appreciations,
'completion_percentage': (completed_appreciations / total_students * 100) if total_students > 0 else 0,
'finalization_percentage': (finalized_appreciations / total_students * 100) if total_students > 0 else 0
}
def create_or_update(
self,
student_id: int,
class_group_id: int,
trimester: int,
data: dict
) -> CouncilAppreciation:
"""Crée ou met à jour une appréciation."""
existing = self.find_by_student_trimester(student_id, class_group_id, trimester)
if existing:
# Mise à jour
for key, value in data.items():
if hasattr(existing, key):
setattr(existing, key, value)
self.commit()
return existing
else:
# Création
appreciation_data = {
'student_id': student_id,
'class_group_id': class_group_id,
'trimester': trimester,
**data
}
appreciation = CouncilAppreciation(**appreciation_data)
self.save(appreciation)
self.commit()
return appreciation
def delete_by_student_trimester(
self,
student_id: int,
class_group_id: int,
trimester: int
) -> bool:
"""Supprime une appréciation spécifique."""
appreciation = self.find_by_student_trimester(student_id, class_group_id, trimester)
if appreciation:
self.delete(appreciation)
return True
return False
def get_students_without_appreciation(
self,
class_group_id: int,
trimester: int
) -> List:
"""Retourne la liste des élèves sans appréciation pour un trimestre."""
from models import Student
# Sous-requête pour les élèves qui ont déjà une appréciation
students_with_appreciation = db.session.query(CouncilAppreciation.student_id).filter_by(
class_group_id=class_group_id,
trimester=trimester
).subquery()
# Élèves sans appréciation
students_without = Student.query.filter_by(
class_group_id=class_group_id
).filter(
~Student.id.in_(students_with_appreciation)
).order_by(Student.last_name, Student.first_name).all()
return students_without

View File

@@ -124,4 +124,60 @@ class AssessmentRepository(BaseRepository[Assessment]):
elif status == 'not_started' and progress_status == 'not_started':
filtered_assessments.append(assessment)
return filtered_assessments
return filtered_assessments
def find_completed_by_class_trimester(self, class_group_id: int, trimester: int) -> List[Assessment]:
"""Trouve les évaluations terminées d'une classe pour un trimestre."""
assessments = Assessment.query.filter_by(
class_group_id=class_group_id,
trimester=trimester
).options(
joinedload(Assessment.class_group),
joinedload(Assessment.exercises).joinedload(Exercise.grading_elements)
).all()
# Filtrer sur progression = 100%
completed_assessments = []
for assessment in assessments:
progress = assessment.grading_progress
if progress.get('status') == 'completed':
completed_assessments.append(assessment)
return completed_assessments
def find_by_class_trimester_with_details(self, class_group_id: int, trimester: int) -> List[Assessment]:
"""Trouve toutes les évaluations d'une classe pour un trimestre avec détails complets."""
return Assessment.query.filter_by(
class_group_id=class_group_id,
trimester=trimester
).options(
joinedload(Assessment.class_group),
joinedload(Assessment.exercises).joinedload(Exercise.grading_elements)
).order_by(Assessment.date.desc()).all()
def get_trimester_statistics(self, class_group_id: int, trimester: int) -> dict:
"""Statistiques des évaluations pour une classe/trimestre."""
assessments = self.find_by_class_trimester_with_details(class_group_id, trimester)
completed = 0
in_progress = 0
not_started = 0
for assessment in assessments:
progress = assessment.grading_progress
status = progress.get('status', 'not_started')
if status == 'completed':
completed += 1
elif status == 'in_progress':
in_progress += 1
else:
not_started += 1
return {
'total': len(assessments),
'completed': completed,
'in_progress': in_progress,
'not_started': not_started,
'completion_percentage': (completed / len(assessments) * 100) if assessments else 0
}

View File

@@ -97,4 +97,34 @@ class GradeRepository(BaseRepository[Grade]):
self.delete(grade)
count += 1
return count
return count
def find_by_student_trimester_with_elements(self, student_id: int, trimester: int) -> List[Grade]:
"""Trouve toutes les notes d'un élève pour un trimestre avec les éléments de notation."""
return Grade.query.join(GradingElement).join(Exercise).join(Assessment).filter(
Grade.student_id == student_id,
Assessment.trimester == trimester
).options(
joinedload(Grade.grading_element).joinedload(GradingElement.exercise).joinedload(Exercise.assessment),
joinedload(Grade.grading_element).joinedload(GradingElement.domain)
).all()
def find_by_class_trimester(self, class_group_id: int, trimester: int) -> List[Grade]:
"""Trouve toutes les notes d'une classe pour un trimestre."""
return Grade.query.join(Student).join(GradingElement).join(Exercise).join(Assessment).filter(
Student.class_group_id == class_group_id,
Assessment.trimester == trimester
).options(
joinedload(Grade.student),
joinedload(Grade.grading_element).joinedload(GradingElement.exercise).joinedload(Exercise.assessment),
joinedload(Grade.grading_element).joinedload(GradingElement.domain)
).all()
def get_student_grades_by_assessment(self, student_id: int, assessment_id: int) -> List[Grade]:
"""Récupère toutes les notes d'un élève pour une évaluation spécifique."""
return Grade.query.join(GradingElement).join(Exercise).filter(
Grade.student_id == student_id,
Exercise.assessment_id == assessment_id
).options(
joinedload(Grade.grading_element).joinedload(GradingElement.exercise)
).all()