diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py index bb3ac15..749b2c6 100644 --- a/Back-End/Subscriptions/models.py +++ b/Back-End/Subscriptions/models.py @@ -54,17 +54,38 @@ class Sibling(models.Model): return "SIBLING" def registration_photo_upload_to(instance, filename): - return f"registration_files/dossier_rf_{instance.pk}/parent/{filename}" + """ + Génère le chemin de stockage pour la photo élève. + Structure : Etablissement/dossier_NomEleve_PrenomEleve/filename + """ + register_form = getattr(instance, 'registrationform', None) + if register_form and register_form.establishment: + est_name = register_form.establishment.name + elif instance.associated_class and instance.associated_class.establishment: + est_name = instance.associated_class.establishment.name + else: + est_name = "unknown_establishment" + + student_last = instance.last_name if instance and instance.last_name else "unknown" + student_first = instance.first_name if instance and instance.first_name else "unknown" + return f"{est_name}/dossier_{student_last}_{student_first}/{filename}" def registration_bilan_form_upload_to(instance, filename): - # On récupère le RegistrationForm lié à l'élève + """ + Génère le chemin de stockage pour les bilans de compétences. + Structure : Etablissement/dossier_NomEleve_PrenomEleve/filename + """ register_form = getattr(instance.student, 'registrationform', None) - if register_form: - pk = register_form.pk + if register_form and register_form.establishment: + est_name = register_form.establishment.name + elif instance.student.associated_class and instance.student.associated_class.establishment: + est_name = instance.student.associated_class.establishment.name else: - # fallback sur l'id de l'élève si pas de registrationform - pk = instance.student.pk - return f"registration_files/dossier_rf_{pk}/bilan/{filename}" + est_name = "unknown_establishment" + + student_last = instance.student.last_name if instance.student else "unknown" + student_first = instance.student.first_name if instance.student else "unknown" + return f"{est_name}/dossier_{student_last}_{student_first}/{filename}" class BilanCompetence(models.Model): student = models.ForeignKey('Subscriptions.Student', on_delete=models.CASCADE, related_name='bilans') diff --git a/Back-End/Subscriptions/templates/pdfs/bilan_competences.html b/Back-End/Subscriptions/templates/pdfs/bilan_competences.html index 529c8c4..7163f5f 100644 --- a/Back-End/Subscriptions/templates/pdfs/bilan_competences.html +++ b/Back-End/Subscriptions/templates/pdfs/bilan_competences.html @@ -4,9 +4,22 @@ Bilan de compétences -

Bilan de compétences

+ + + + + +
+ + + {% if establishment.logo_path %} + + {% endif %} + + +
+ + +
{{ establishment.name }}
+ {% if establishment.address %} +
{{ establishment.address }}
+ {% endif %} +
+
+ + + + +
+
Généré avec
+ {% if product.logo_path %} +
+ {% endif %} +
{{ product.name }}
+
+
+ +
+

Bilan de compétences

+
+
- Élève : {{ student.last_name }} {{ student.first_name }}
- Niveau : {{ student.level }}
- Classe : {{ student.class_name }}
- Période : {{ period }}
- Date : {{ date }} + + + + + + + + + + + + + +
Élève : {{ student.last_name }} {{ student.first_name }}Date : {{ date }}
Niveau : {{ student.level }}Période : {{ period }}
Classe : {{ student.class_name }}
{% for domaine in domaines %} @@ -72,41 +146,33 @@ {% endfor %} -
-
-
- Appréciation générale / Commentaire : -
- -
-
-
-
-
-
-
-
-
-
- Date : -   -
-
- Signature : -   -
+ \ No newline at end of file diff --git a/Back-End/Subscriptions/views/student_competencies_views.py b/Back-End/Subscriptions/views/student_competencies_views.py index b803249..43f14fa 100644 --- a/Back-End/Subscriptions/views/student_competencies_views.py +++ b/Back-End/Subscriptions/views/student_competencies_views.py @@ -175,13 +175,43 @@ class StudentCompetencyListCreateView(APIView): domaine_dict["categories"].append(categorie_dict) if domaine_dict["categories"]: result.append(domaine_dict) + + establishment = None + if student.associated_class and student.associated_class.establishment: + establishment = student.associated_class.establishment + else: + try: + establishment = student.registrationform.establishment + except Exception: + establishment = None + + establishment_logo_path = None + if establishment and establishment.logo: + try: + if establishment.logo.path and os.path.exists(establishment.logo.path): + establishment_logo_path = establishment.logo.path + except Exception: + establishment_logo_path = None + + n3wt_logo_path = os.path.join(settings.BASE_DIR, 'static', 'img', 'logo_min.svg') + if not os.path.exists(n3wt_logo_path): + n3wt_logo_path = None context = { "student": { "first_name": student.first_name, "last_name": student.last_name, "level": student.level, - "class_name": student.associated_class.atmosphere_name, + "class_name": student.associated_class.atmosphere_name if student.associated_class else "Non assignée", + }, + "establishment": { + "name": establishment.name if establishment else "Établissement", + "address": establishment.address if establishment else "", + "logo_path": establishment_logo_path, + }, + "product": { + "name": "n3wt-school", + "logo_path": n3wt_logo_path, }, "period": period, "date": date.today().strftime("%d/%m/%Y"), diff --git a/Back-End/static/img/n3wt.svg b/Back-End/static/img/n3wt.svg new file mode 100644 index 0000000..0d88549 --- /dev/null +++ b/Back-End/static/img/n3wt.svg @@ -0,0 +1,7 @@ + + + + + n3wt + school + diff --git a/Front-End/src/app/[locale]/admin/grades/page.js b/Front-End/src/app/[locale]/admin/grades/page.js index c3416d3..ad6eb1e 100644 --- a/Front-End/src/app/[locale]/admin/grades/page.js +++ b/Front-End/src/app/[locale]/admin/grades/page.js @@ -10,7 +10,8 @@ import { Pencil, Trash2, Save, - Download + Download, + FileText, } from 'lucide-react'; import SectionHeader from '@/components/SectionHeader'; import Table from '@/components/Table'; @@ -435,6 +436,37 @@ export default function Page() { ); }; + const getBilanForStudent = (student) => { + const bilans = Array.isArray(student?.bilans) ? student.bilans : []; + if (!bilans.length) return null; + + const currentPeriodStr = currentPeriodValue + ? getPeriodString( + currentPeriodValue, + selectedEstablishmentEvaluationFrequency, + selectedSchoolYear + ) + : null; + + if (currentPeriodStr) { + const exact = bilans.find( + (bilan) => bilan?.period === currentPeriodStr && bilan?.file + ); + if (exact) return exact; + } + + const schoolYearSuffix = `_${selectedSchoolYear}`; + const sameYearBilans = bilans.filter( + (bilan) => bilan?.file && bilan?.period?.endsWith(schoolYearSuffix) + ); + + if (!sameYearBilans.length) return null; + + return [...sameYearBilans].sort( + (a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0) + )[0]; + }; + const columns = [ { name: 'Photo', transform: () => null }, { name: 'Élève', transform: () => null }, @@ -510,8 +542,23 @@ export default function Page() { 0 ); case 'Actions': + const bilan = getBilanForStudent(student); + return (
+ {bilan?.file && ( + e.stopPropagation()} + className="flex items-center gap-1 px-2 py-1 rounded text-xs font-medium bg-cyan-100 text-cyan-700 hover:bg-cyan-200 transition whitespace-nowrap" + title={`Télécharger le bilan de compétences (${bilan.period})`} + > + + Bilan + + )}