diff --git a/app/forms/assiduite/ajout_assiduite_etud.py b/app/forms/assiduite/ajout_assiduite_etud.py index 44b7f9c7e..deeec72c5 100644 --- a/app/forms/assiduite/ajout_assiduite_etud.py +++ b/app/forms/assiduite/ajout_assiduite_etud.py @@ -161,3 +161,30 @@ class AjoutJustificatifEtudForm(AjoutAssiOrJustForm): validators=[DataRequired(message="This field is required.")], ) fichiers = MultipleFileField(label="Ajouter des fichiers") + + +class ChoixDateForm(FlaskForm): + def __init__(self, *args, **kwargs): + "Init form, adding a filed for our error messages" + super().__init__(*args, **kwargs) + self.ok = True + self.error_messages: list[str] = [] # used to report our errors + + def set_error(self, err_msg, field=None): + "Set error message both in form and field" + self.ok = False + self.error_messages.append(err_msg) + if field: + field.errors.append(err_msg) + + date = StringField( + "Date", + validators=[validators.Length(max=10)], + render_kw={ + "class": "datepicker", + "size": 10, + "id": "date", + }, + ) + submit = SubmitField("Enregistrer") + cancel = SubmitField("Annuler", render_kw={"formnovalidate": True}) diff --git a/app/models/assiduites.py b/app/models/assiduites.py index ccfdd81e9..3d6a296cf 100644 --- a/app/models/assiduites.py +++ b/app/models/assiduites.py @@ -4,6 +4,7 @@ from datetime import datetime from flask_login import current_user from flask_sqlalchemy.query import Query +from sqlalchemy.exc import DataError from app import db, log, g, set_sco_dept from app.models import ( @@ -249,50 +250,58 @@ class Assiduite(ScoDocModel): sco_abs_notification.abs_notify(etud.id, nouv_assiduite.date_debut) return nouv_assiduite - def set_moduleimpl(self, moduleimpl_id: int | str) -> bool: - """TODO""" - # je ne comprend pas cette fonction WIP - # moduleimpl_id peut être == "autre", ce qui plante - # ci-dessous un fix temporaire en attendant explication de @iziram - if moduleimpl_id is None: - raise ScoValueError("invalid moduleimpl_id") + def set_moduleimpl(self, moduleimpl_id: int | str): + """Mise à jour du moduleimpl_id + Les valeurs du champs "moduleimpl_id" possibles sont : + - (un id classique) + - ("autre" ou "") + - None (pas de moduleimpl_id) + Si la valeur est "autre" il faut: + - mettre à None assiduité.moduleimpl_id + - mettre à jour assiduite.external_data["module"] = "autre" + En fonction de la configuration du semestre la valeur `None` peut-être considérée comme invalide. + - Il faudra donc vérifier que ce n'est pas le cas avant de mettre à jour l'assiduité + """ + moduleimpl: ModuleImpl = None try: - moduleimpl_id_int = int(moduleimpl_id) - except ValueError as exc: - raise ScoValueError("invalid moduleimpl_id") from exc - # /fix - moduleimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id_int) - if moduleimpl is not None: - # Vérification de l'inscription de l'étudiant + # ne lève une erreur que si moduleimpl_id est une chaine de caractère non parsable (parseInt) + moduleimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id) + # moduleImpl est soit : + # - None si moduleimpl_id==None + # - None si moduleimpl_id== non reconnu + # - ModuleImpl si valide + + # Vérification ModuleImpl not None (raise ScoValueError) + if moduleimpl is None and self._check_force_module(moduleimpl): + # Ici uniquement si on est autorisé à ne pas avoir de module + self.moduleimpl_id = None + return + + # Vérification Inscription ModuleImpl (raise ScoValueError) 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): + + except DataError: + # On arrive ici si moduleimpl_id == "autre" ou moduleimpl_id == non parsé + + if moduleimpl_id != "autre": + raise ScoValueError("Module non reconnu") + + # Configuration de external_data pour Module Autre + # Si self.external_data None alors on créé un dictionnaire {"module": "autre"} + # Sinon on met à jour external_data["module"] à "autre" + if self.external_data is None: - self.external_data = {"module": moduleimpl_id} + self.external_data = {"module": "autre"} else: - self.external_data["module"] = moduleimpl_id + self.external_data["module"] = "autre" + + # Dans tous les cas une fois fait, assiduite.moduleimpl_id doit être None 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=self.etudiant.dept_id) - - if force: - raise ScoValueError("Module non renseigné") - return True + # Ici pas de vérification du force module car on l'a mis dans "external_data" def supprime(self): "Supprime l'assiduité. Log et commit." @@ -338,6 +347,40 @@ class Assiduite(ScoDocModel): return "Non spécifié" if traduire else None + def get_saisie(self) -> str: + """ + retourne le texte "saisie le par " + """ + + date: str = self.entry_date.strftime("%d/%m/%Y à %H:%M") + utilisateur: str = "" + if self.user != None: + self.user: User + utilisateur = f"par {self.user.get_prenomnom()}" + + return f"saisie le {date} {utilisateur}" + + def _check_force_module(self, moduleimpl: ModuleImpl) -> bool: + # 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=self.etudiant.dept_id) + + if force: + raise ScoValueError("Module non renseigné") + + return True + class Justificatif(ScoDocModel): """ diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index 487556aba..5f8287e60 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -394,6 +394,10 @@ def get_assiduites_stats( if "etat" in filtered else ["absent", "present", "retard"] ) + + # être sur que les états sont corrects + etats = [etat for etat in etats if etat in ["absent", "present", "retard"]] + # Préparation du dictionnaire de retour avec les valeurs du calcul count: dict = calculator.to_dict(only_total=False) for etat in etats: diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js index 55d0c6afa..9f7e69e7a 100644 --- a/app/static/js/assiduites.js +++ b/app/static/js/assiduites.js @@ -110,11 +110,12 @@ function validateSelectors(btn) { getAssiduitesFromEtuds(true); - document.querySelector(".selectors").disabled = true; - $("#tl_date").datepicker("option", "disabled", true); + // document.querySelector(".selectors").disabled = true; + // $("#tl_date").datepicker("option", "disabled", true); generateMassAssiduites(); generateAllEtudRow(); - btn.remove(); + // btn.remove(); + btn.textContent = "Actualiser"; onlyAbs(); }; @@ -533,6 +534,7 @@ function massAction() { * puis on ajoute les événements associés */ function generateMassAssiduites() { + if (readOnly || document.querySelector(".mass-selection") != null) return; const content = document.getElementById("content"); const mass = document.createElement("div"); @@ -1411,7 +1413,8 @@ function generateEtudRow( assi += ``; } }); - const conflit = assiduite.type == "conflit" ? "conflit" : ""; + if (readOnly) assi = ""; + const conflit = assiduite.type == "conflit" && !readOnly ? "conflit" : ""; const pdp_url = `${getUrl()}/api/etudiant/etudid/${etud.id}/photo?size=small`; let defdem = ""; @@ -1543,11 +1546,11 @@ function generateAllEtudRow() { return; } - if (!document.querySelector(".selectors")?.disabled) { - return; - } - - document.querySelector(".etud_holder").innerHTML = ""; + // if (!document.querySelector(".selectors")?.disabled) { + // return; + // } + const etud_hodler = document.querySelector(".etud_holder"); + if (etud_hodler) etud_hodler.innerHTML = ""; etuds_ids = Object.keys(etuds).sort((a, b) => etuds[a].nom > etuds[b].nom ? 1 : etuds[b].nom > etuds[a].nom ? -1 : 0 ); diff --git a/app/templates/assiduites/pages/calendrier.j2 b/app/templates/assiduites/pages/calendrier.j2 deleted file mode 100644 index cd8feb583..000000000 --- a/app/templates/assiduites/pages/calendrier.j2 +++ /dev/null @@ -1,741 +0,0 @@ -{% block pageContent %} -{% include "assiduites/widgets/alert.j2" %} - -
- {{minitimeline | safe }} -

Assiduité de {{sco.etud.html_link_fiche()|safe}}

- -
- - - -
- -
- -
-
- Année scolaire 2022-2023Changer - année: - - - Assiduité de {{sco.etud.nomprenom}} -
- -
-

Calendrier

-

Code couleur

-
    -
  • → présence de l'étudiant lors de la période -
  • -
  • → la période n'est pas travaillée -
  • -
  • → absence de l'étudiant lors de la période -
  • -
  • → absence justifiée -
  • -
  • → retard de l'étudiant lors de la période -
  • -
  • → retard justifié -
  • - -
  • → la période est couverte par un - justificatif valide
  • -
  • → la période est - couverte par un justificatif non valide -
  • -
  • → la période - a un justificatif en attente de validation -
  • -
- - -

Vous pouvez passer le curseur sur les jours colorés afin de voir les informations supplémentaires

-
-
    -
  • présence -
  • -
  • non travaillé -
  • -
  • absence -
  • -
  • absence justifiée -
  • -
  • retard -
  • -
  • retard justifié -
  • -
  • - justificatif valide
  • -
  • justificatif non valide -
  • -
-
- - - - -{% endblock pageContent %} diff --git a/app/templates/assiduites/pages/calendrier_assi_etud.j2 b/app/templates/assiduites/pages/calendrier_assi_etud.j2 new file mode 100644 index 000000000..3eaedb52b --- /dev/null +++ b/app/templates/assiduites/pages/calendrier_assi_etud.j2 @@ -0,0 +1,596 @@ +{% block pageContent %} +{% include "assiduites/widgets/alert.j2" %} + +
+

Assiduité de {{sco.etud.html_link_fiche()|safe}}

+ +
+ + + +
+ +
+ {% for mois,jours in calendrier.items() %} +
+

{{mois}}

+
+ {% for jour in jours %} + {% if jour.is_non_work() %} +
+ {{jour.get_nom()}} + {% else %} +
+ {% endif %} + {% if mode_demi %} + {% if not jour.is_non_work() %} + {{jour.get_nom()}} + + + {% endif %} + {% else %} + {% if not jour.is_non_work() %} + {{jour.get_nom(False)}} + {% endif %} + {% endif %} + + {% if not jour.is_non_work() and jour.has_assiduites()%} + +
+
+ Assiduité du +
+ {{jour.get_date()}} + {{jour.generate_minitimeline() | safe}} +
+
+ + {% endif %} +
+ + {% endfor %} +
+
+ {% endfor %} +
+
+ Année scolaire 2022-2023Changer + année: + + + Assiduité de {{sco.etud.nomprenom}} +
+ +
+

Calendrier

+

Code couleur

+
    +
  • → présence de l'étudiant lors de la période +
  • +
  • → la période n'est pas travaillée +
  • +
  • → absence de l'étudiant lors de la période +
  • +
  • → absence justifiée +
  • +
  • → retard de l'étudiant lors de la période +
  • +
  • → retard justifié +
  • + +
  • → la période est couverte par un + justificatif valide
  • +
  • → la période est + couverte par un justificatif non valide +
  • +
  • → la période + a un justificatif en attente de validation +
  • +
+ + +

Vous pouvez passer le curseur sur les jours colorés afin de voir les informations supplémentaires

+
+
    +
  • présence +
  • +
  • non travaillé +
  • +
  • absence +
  • +
  • absence justifiée +
  • +
  • retard +
  • +
  • retard justifié +
  • +
  • + justificatif valide
  • +
  • justificatif non valide +
  • +
+
+ + + + +{% endblock pageContent %} diff --git a/app/templates/assiduites/pages/choix_date.j2 b/app/templates/assiduites/pages/choix_date.j2 new file mode 100644 index 000000000..d743ac15e --- /dev/null +++ b/app/templates/assiduites/pages/choix_date.j2 @@ -0,0 +1,30 @@ +{% extends "sco_page.j2" %} +{% import 'wtf.j2' as wtf %} + +{% block styles %} + {{super()}} + +{% endblock %} + +{% block app_content %} + {% for err_msg in form.error_messages %} +
+ {{ err_msg }} +
+ {% endfor %} +

La date courante n'est pas dans le semestre ({{deb}} -> {{fin}})

+

Choissez une autre date

+ +
+ {{ form.hidden_tag() }} + {{ form.date.label }} : {{ form.date }} +
+ {{ form.submit }} {{ form.cancel }} +
+
+ +{% endblock app_content %} +{% block scripts %} +{{ super() }} + +{% endblock scripts %} \ No newline at end of file diff --git a/app/templates/assiduites/pages/signal_assiduites_etud.j2 b/app/templates/assiduites/pages/signal_assiduites_etud.j2 index 2284604a7..ea4530952 100644 --- a/app/templates/assiduites/pages/signal_assiduites_etud.j2 +++ b/app/templates/assiduites/pages/signal_assiduites_etud.j2 @@ -102,6 +102,9 @@ setupTimeLine(() => { + if(document.querySelector('.etud_holder .placeholder') != null){ + generateAllEtudRow(); + } updateJustifyBtn(); }); diff --git a/app/templates/assiduites/pages/signal_assiduites_group.j2 b/app/templates/assiduites/pages/signal_assiduites_group.j2 index 15fd6a48f..238dd25c7 100644 --- a/app/templates/assiduites/pages/signal_assiduites_group.j2 +++ b/app/templates/assiduites/pages/signal_assiduites_group.j2 @@ -26,11 +26,9 @@
+ {% if readonly == "false" %} {{timeline|safe}} - - - {% if readonly == "false" %}