diff --git a/app/api/justificatifs.py b/app/api/justificatifs.py index b398b83cb..e90098e5e 100644 --- a/app/api/justificatifs.py +++ b/app/api/justificatifs.py @@ -646,8 +646,8 @@ def justif_import(justif_id: int = None): return json_error(404, err.args[0]) -@bp.route("/justificatif//export/", methods=["POST"]) -@api_web_bp.route("/justificatif//export/", methods=["POST"]) +@bp.route("/justificatif//export/", methods=["GET", "POST"]) +@api_web_bp.route("/justificatif//export/", methods=["GET", "POST"]) @scodoc @login_required @permission_required(Permission.AbsChange) diff --git a/app/models/assiduites.py b/app/models/assiduites.py index 47152e3b6..937e11c43 100644 --- a/app/models/assiduites.py +++ b/app/models/assiduites.py @@ -204,6 +204,40 @@ class Assiduite(db.Model): sco_abs_notification.abs_notify(etud.id, nouv_assiduite.date_debut) return nouv_assiduite + def set_moduleimpl(self, moduleimpl_id: int | str) -> bool: + moduleimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id) + if moduleimpl is not None: + # Vérification de l'inscription de l'étudiant + if moduleimpl.est_inscrit(self.etudiant): + self.moduleimpl_id = moduleimpl.id + else: + raise ScoValueError("L'étudiant n'est pas inscrit au module") + elif isinstance(moduleimpl_id, str): + if self.external_data is None: + self.external_data = {"module": moduleimpl_id} + else: + self.external_data["module"] = moduleimpl_id + self.moduleimpl_id = None + else: + # Vérification si module forcé + formsemestre: FormSemestre = get_formsemestre_from_data( + { + "etudid": self.etudid, + "date_debut": self.date_debut, + "date_fin": self.date_fin, + } + ) + force: bool + + if formsemestre: + force = is_assiduites_module_forced(formsemestre_id=formsemestre.id) + else: + force = is_assiduites_module_forced(dept_id=etud.dept_id) + + if force: + raise ScoValueError("Module non renseigné") + return True + def supprimer(self): from app.scodoc import sco_assiduites as scass diff --git a/app/static/js/date_utils.js b/app/static/js/date_utils.js index 3565ea515..298967438 100644 --- a/app/static/js/date_utils.js +++ b/app/static/js/date_utils.js @@ -481,7 +481,7 @@ class ScoDocDateTimePicker extends HTMLElement { } else { // Mettre à jour la valeur de l'input caché avant la soumission this.hiddenInput.value = this.isValid() - ? this.valueAsDate.toIsoUtcString() + ? this.valueAsDate.toFakeIso() : ""; } }); diff --git a/app/tables/liste_assiduites.py b/app/tables/liste_assiduites.py index 21c69cbf7..6a61de53c 100644 --- a/app/tables/liste_assiduites.py +++ b/app/tables/liste_assiduites.py @@ -17,6 +17,7 @@ class ListeAssiJusti(tb.Table): """ NB_PAR_PAGE: int = 25 + MAX_PAR_PAGE: int = 200 def __init__( self, @@ -221,7 +222,7 @@ class RowAssiJusti(tb.Row): # Ajout de l'étudiant self.table: ListeAssiJusti if self.table.options.show_etu: - self._etud() + self._etud(lien_redirection) # Type d'objet self._type() @@ -287,7 +288,7 @@ class RowAssiJusti(tb.Row): self.add_cell("obj_type", "Type", obj_type) - def _etud(self) -> None: + def _etud(self, lien_redirection) -> None: etud = self.etud self.table.group_titles.update( { @@ -357,7 +358,7 @@ class RowAssiJusti(tb.Row): obj_id=self.ligne["obj_id"], scodoc_dept=g.scodoc_dept, ) - html.append(f'Détails') # utiliser url_for + html.append(f'ℹ️') # utiliser url_for # Modifier url = url_for( @@ -367,7 +368,7 @@ class RowAssiJusti(tb.Row): obj_id=self.ligne["obj_id"], scodoc_dept=g.scodoc_dept, ) - html.append(f'Modifier') # utiliser url_for + html.append(f'📝') # utiliser url_for # Supprimer url = url_for( @@ -377,11 +378,9 @@ class RowAssiJusti(tb.Row): obj_id=self.ligne["obj_id"], scodoc_dept=g.scodoc_dept, ) - html.append(f'Supprimer') # utiliser url_for + html.append(f'') # utiliser url_for - self.add_cell( - "actions", "Actions", " ".join(html), raw_content="test", no_excel=True - ) + self.add_cell("actions", "Actions", " ".join(html), no_excel=True) class Filtre: @@ -492,6 +491,8 @@ class Options: ): self.page: int = page self.nb_ligne_page: int = nb_ligne_page + if self.nb_ligne_page is not None: + self.nb_ligne_page = min(nb_ligne_page, ListeAssiJusti.MAX_PAR_PAGE) self.show_pres: bool = show_pres in Options.VRAI self.show_reta: bool = show_reta in Options.VRAI @@ -503,9 +504,13 @@ class Options: def remplacer(self, **kwargs): for k, v in kwargs.items(): if k.startswith("show_"): - self.__setattr__(k, v in Options.VRAI) + setattr(self, k, v in Options.VRAI) elif k in ["page", "nb_ligne_page"]: - self.__setattr__(k, int(v)) + setattr(self, k, int(v)) + if k == "nb_ligne_page": + self.nb_ligne_page = min( + self.nb_ligne_page, ListeAssiJusti.MAX_PAR_PAGE + ) class Data: diff --git a/app/templates/assiduites/pages/ajout_justificatif.j2 b/app/templates/assiduites/pages/ajout_justificatif.j2 index d74737146..c295fc9ec 100644 --- a/app/templates/assiduites/pages/ajout_justificatif.j2 +++ b/app/templates/assiduites/pages/ajout_justificatif.j2 @@ -2,8 +2,6 @@ {% block pageContent %}

Justifier des absences ou retards

- {% include "assiduites/widgets/tableau_base.j2" %} -
@@ -58,28 +56,9 @@
- - {% include "assiduites/widgets/tableau_justi.j2" %} + {{tableau | safe }}
-
- -

Gestion des justificatifs

-

- Faites - clic droit sur une ligne du tableau pour afficher le menu - contextuel : -

    -
  • Détails : Affiche les détails du justificatif sélectionné
  • -
  • Editer : Permet de modifier le justificatif (dates, etat, ajouter/supprimer fichier etc)
  • -
  • Supprimer : Permet de supprimer le justificatif (Action Irréversible)
  • -
-

- -

Cliquer sur l'icone d'entonoir afin de filtrer le tableau des justificatifs

- -
-
\ No newline at end of file diff --git a/app/views/assiduites.py b/app/views/assiduites.py index 699e0e3a5..6427e84a5 100644 --- a/app/views/assiduites.py +++ b/app/views/assiduites.py @@ -266,13 +266,6 @@ def signal_assiduites_etud(): if etud.dept_id != g.scodoc_dept_id: abort(404, "étudiant inexistant dans ce département") - # Récupération de la date (par défaut la date du jour) - date = request.args.get("date", datetime.date.today().isoformat()) - heures: list[str] = [ - request.args.get("heure_deb", ""), - request.args.get("heure_fin", ""), - ] - # gestion évaluations (Appel à la page depuis les évaluations) saisie_eval: bool = request.args.get("saisie_eval") is not None @@ -305,26 +298,13 @@ def signal_assiduites_etud(): ], ) - # Gestion des horaires (journée, matin, soir) - - morning = ScoDocSiteConfig.assi_get_rounded_time("assi_morning_time", "08:00:00") - lunch = ScoDocSiteConfig.assi_get_rounded_time("assi_lunch_time", "13:00:00") - afternoon = ScoDocSiteConfig.assi_get_rounded_time( - "assi_afternoon_time", "18:00:00" - ) - - # Gestion du selecteur de moduleimpl (pour le tableau différé) - select = f""" - - """ - tableau = _preparer_tableau( - etud, + liste_assi.Data.from_etudiants( + etud, + ), filename=f"assiduite-{etudid}", afficher_etu=False, - filtre=liste_assi.Filtre(type_obj=0), + filtre=liste_assi.Filtre(type_obj=1), options=liste_assi.Options(show_module=True), ) if not tableau[0]: @@ -393,7 +373,7 @@ def liste_assiduites_etud(): if etud.dept_id != g.scodoc_dept_id: abort(404, "étudiant inexistant dans ce département") - # Gestion d'une assiduité unique (redirigé depuis le calendrier) + # Gestion d'une assiduité unique (redirigé depuis le calendrier) TODO-Assiduites assiduite_id: int = request.args.get("assiduite_id", -1) # Préparation de la page @@ -409,18 +389,25 @@ def liste_assiduites_etud(): "css/assiduites.css", ], ) + tableau = _preparer_tableau( + liste_assi.Data.from_etudiants( + etud, + ), + filename=f"assiduites-justificatifs-{etudid}", + afficher_etu=False, + filtre=liste_assi.Filtre(type_obj=0), + options=liste_assi.Options(show_module=True), + ) + if not tableau[0]: + return tableau[1] # Peuplement du template jinja return HTMLBuilder( header, render_template( "assiduites/pages/liste_assiduites.j2", sco=ScoData(etud), - date=datetime.date.today().isoformat(), assi_id=assiduite_id, - assi_limit_annee=sco_preferences.get_preference( - "assi_limit_annee", - dept_id=g.scodoc_dept_id, - ), + tableau=tableau[1], ), ).build() @@ -517,6 +504,19 @@ def ajout_justificatif_etud(): ], ) + tableau = _preparer_tableau( + liste_assi.Data.from_etudiants( + etud, + ), + filename=f"justificatifs-{etudid}", + afficher_etu=False, + filtre=liste_assi.Filtre(type_obj=2), + options=liste_assi.Options(show_module=False, show_desc=True), + afficher_options=False, + ) + if not tableau[0]: + return tableau[1] + # Peuplement du template jinja return HTMLBuilder( header, @@ -529,6 +529,7 @@ def ajout_justificatif_etud(): ), assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"), assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"), + tableau=tableau[1], ), ).build() @@ -1061,11 +1062,12 @@ def visu_assi_group(): def _preparer_tableau( - *etudiants: Identite, + data: liste_assi.Data, filename: str = "tableau-assiduites", afficher_etu: bool = True, filtre: liste_assi.Filtre = None, options: liste_assi.Options = None, + afficher_options: bool = True, ) -> tuple[bool, "Response"]: """ _preparer_tableau prépare un tableau d'assiduités / justificatifs @@ -1120,7 +1122,7 @@ def _preparer_tableau( ) table: liste_assi.ListeAssiJusti = liste_assi.ListeAssiJusti( - table_data=liste_assi.Data.from_etudiants(*etudiants), + table_data=data, options=options, filtre=filtre, ) @@ -1138,6 +1140,7 @@ def _preparer_tableau( tableau=table.html(), total_pages=table.total_pages, options=options, + afficher_options=afficher_options, ) @@ -1186,6 +1189,100 @@ def tableau_assiduite_actions(): obj_id=obj_id, moduleimpl=module, ) + # Cas des POSTS + if obj_type == "assiduite": + try: + _action_modifier_assiduite(objet) + except ScoValueError as error: + raise ScoValueError(error.args[0], request.referrer) from error + flash("L'assiduité a bien été modifiée.") + else: + try: + _action_modifier_justificatif(objet) + except ScoValueError as error: + raise ScoValueError(error.args[0], request.referrer) from error + flash("Le justificatif a bien été modifié.") + return redirect(request.form["table_url"]) + + +def _action_modifier_assiduite(assi: Assiduite): + form = request.form + + # Gestion de l'état + etat = scu.EtatAssiduite.get(form["etat"]) + if etat is not None: + assi.etat = etat + if etat == scu.EtatAssiduite.PRESENT: + assi.est_just = False + else: + assi.est_just = len(get_assiduites_justif(assi.assiduite_id, False)) > 0 + + # Gestion de la description + assi.description = form["description"] + + module: str = form["moduleimpl_select"] + + if module == "": + module = None + else: + try: + module = int(module) + except ValueError: + pass + + assi.set_moduleimpl(module) + + db.session.add(assi) + db.session.commit() + scass.simple_invalidate_cache(assi.to_dict(True), assi.etudid) + + +def _action_modifier_justificatif(justi: Justificatif): + form = request.form + + # Gestion des Dates + + date_debut: datetime = scu.is_iso_formated(form["date_debut"], True) + date_fin: datetime = scu.is_iso_formated(form["date_fin"], True) + if date_debut is None or date_fin is None or date_fin < date_debut: + raise ScoValueError("Dates invalides", request.referrer) + justi.date_debut = date_debut + justi.date_fin = date_fin + + # Gestion de l'état + etat = scu.EtatJustificatif.get(form["etat"]) + if etat is not None: + justi.etat = etat + else: + raise ScoValueError("État invalide", request.referrer) + + # Gestion de la raison + justi.raison = form["raison"] + + # Gestion des fichiers + files = request.files.getlist("justi_fich") + if len(files) != 0: + files = request.files.values() + + archive_name: str = justi.fichier + # Utilisation de l'archiver de justificatifs + archiver: JustificatifArchiver = JustificatifArchiver() + + for fich in files: + archive_name, _ = archiver.save_justificatif( + justi.etudiant, + filename=fich.filename, + data=fich.stream.read(), + archive_name=archive_name, + user_id=current_user.id, + ) + + justi.fichier = archive_name + + db.session.add(justi) + db.session.commit() + scass.compute_assiduites_justified(justi.etudid, reset=True) + scass.simple_invalidate_cache(justi.to_dict(True), justi.etudid) def _preparer_objet(