From a4072efe4c9d641c9a3df7d1912887a0dddbc4e2 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 23 Jan 2023 07:38:47 -0300 Subject: [PATCH] =?UTF-8?q?Saisie=20automatique=20des=20d=C3=A9cisions=20d?= =?UTF-8?q?e=20jury=20BUT=20pour=20semestres=20pairs=20ou=20impairs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/but/jury_but.py | 70 +++++++++++++------ app/but/jury_but_validation_auto.py | 30 ++++---- app/pe/pe_jurype.py | 5 +- app/scodoc/sco_codes_parcours.py | 21 +++--- app/scodoc/sco_cursus_dut.py | 2 +- app/scodoc/sco_formsemestre_validation.py | 4 +- .../but/formsemestre_validation_auto_but.html | 18 +++-- app/views/notes.py | 18 +++-- 8 files changed, 107 insertions(+), 61 deletions(-) diff --git a/app/but/jury_but.py b/app/but/jury_but.py index 6ef89a37b..244989edd 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -693,20 +693,20 @@ class DecisionsProposeesAnnee(DecisionsProposees): db.session.commit() - def record(self, code: str, no_overwrite=False): + def record(self, code: str, no_overwrite=False) -> bool: """Enregistre le code de l'année, et au besoin l'autorisation d'inscription. Si no_overwrite, ne fait rien si un code est déjà enregistré. Si l'étudiant est DEM ou DEF, ne fait rien. """ if self.inscription_etat != scu.INSCRIT: - return + return False if code and not code in self.codes: raise ScoValueError( f"code annee {html.escape(code)} invalide pour formsemestre {html.escape(self.formsemestre)}" ) if code == self.code_valide or (self.code_valide is not None and no_overwrite): self.recorded = True - return # no change + return False # no change if self.validation: db.session.delete(self.validation) db.session.commit() @@ -746,9 +746,10 @@ class DecisionsProposeesAnnee(DecisionsProposees): next_semestre_id, ) - self.recorded = True db.session.commit() + self.recorded = True self.invalidate_formsemestre_cache() + return True def invalidate_formsemestre_cache(self): "invalide le résultats des deux formsemestres" @@ -759,13 +760,20 @@ class DecisionsProposeesAnnee(DecisionsProposees): if self.formsemestre_pair is not None: sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre_pair.id) - def record_all(self, no_overwrite: bool = True): + def record_all( + self, no_overwrite: bool = True, only_validantes: bool = False + ) -> bool: """Enregistre les codes qui n'ont pas été spécifiés par le formulaire, et sont donc en mode "automatique". - Si "à cheval", ne modifie pas les codes UE de l'année scolaire précédente. - Pour les RCUE: n'enregistre que si la nouvelle décision est plus favorable que l'ancienne. + + Si only_validantes, n'enregistre que des décisions "validantes" de droit: ADM ou CMP. + + Return: True si au moins un code modifié et enregistré. """ - # Toujours valider dans l'ordre UE, RCUE, Année: + modif = False + # Toujours valider dans l'ordre UE, RCUE, Année annee_scolaire = self.formsemestre.annee_scolaire() # UEs for dec_ue in self.decisions_ues.values(): @@ -774,25 +782,40 @@ class DecisionsProposeesAnnee(DecisionsProposees): ) and dec_ue.formsemestre.annee_scolaire() == annee_scolaire: # rappel: le code par défaut est en tête code = dec_ue.codes[0] if dec_ue.codes else None - # enregistre le code jury seulement s'il n'y a pas déjà de code - # (no_overwrite=True) sauf en mode test yaml - dec_ue.record(code, no_overwrite=no_overwrite) - # RCUE : enregistre seulement si pas déjà validé "mieux" + if (not only_validantes) or code in sco_codes.CODES_UE_VALIDES_DE_DROIT: + # enregistre le code jury seulement s'il n'y a pas déjà de code + # (no_overwrite=True) sauf en mode test yaml + modif |= dec_ue.record(code, no_overwrite=no_overwrite) + # RCUE : for dec_rcue in self.decisions_rcue_by_niveau.values(): code = dec_rcue.codes[0] if dec_rcue.codes else None - if (not dec_rcue.recorded) and ( - (not dec_rcue.validation) - or BUT_CODES_ORDERED.get(dec_rcue.validation.code, 0) - < BUT_CODES_ORDERED.get(code, 0) + if ( + (not dec_rcue.recorded) + and ( # enregistre seulement si pas déjà validé "mieux" + (not dec_rcue.validation) + or BUT_CODES_ORDERED.get(dec_rcue.validation.code, 0) + < BUT_CODES_ORDERED.get(code, 0) + ) + and ( # décision validante de droit ? + ( + (not only_validantes) + or code in sco_codes.CODES_RCUE_VALIDES_DE_DROIT + ) + ) ): - dec_rcue.record(code, no_overwrite=no_overwrite) + modif |= dec_rcue.record(code, no_overwrite=no_overwrite) # Année: if not self.recorded: # rappel: le code par défaut est en tête code = self.codes[0] if self.codes else None # enregistre le code jury seulement s'il n'y a pas déjà de code # (no_overwrite=True) sauf en mode test yaml - self.record(code, no_overwrite=no_overwrite) + if ( + not only_validantes + ) or code in sco_codes.CODES_ANNEE_BUT_VALIDES_DE_DROIT: + modif |= self.record(code, no_overwrite=no_overwrite) + + return modif def erase(self, only_one_sem=False): """Efface les décisions de jury de cet étudiant @@ -1005,23 +1028,23 @@ class DecisionsProposeesRCUE(DecisionsProposees): return f"""<{self.__class__.__name__} rcue={self.rcue} valid={self.code_valide } codes={self.codes} explanation={self.explanation}""" - def record(self, code: str, no_overwrite=False): + def record(self, code: str, no_overwrite=False) -> bool: """Enregistre le code RCUE. Note: - si le RCUE est ADJ, les UE non validées sont passées à ADJ XXX on pourra imposer ici d'autres règles de cohérence """ if self.rcue is None: - return # pas de RCUE a enregistrer + return False # pas de RCUE a enregistrer if self.inscription_etat != scu.INSCRIT: - return + return False if code and not code in self.codes: raise ScoValueError( f"code UE invalide pour ue_id={self.ue.id}: {html.escape(code)}" ) if code == self.code_valide or (self.code_valide is not None and no_overwrite): self.recorded = True - return # no change + return False # no change parcours_id = self.parcour.id if self.parcour is not None else None if self.validation: db.session.delete(self.validation) @@ -1072,6 +1095,7 @@ class DecisionsProposeesRCUE(DecisionsProposees): ) self.code_valide = code # mise à jour état self.recorded = True + return True def erase(self): """Efface la décision de jury de cet étudiant pour cet RCUE""" @@ -1203,9 +1227,10 @@ class DecisionsProposeesUE(DecisionsProposees): self.codes = [sco_codes.AJ, sco_codes.ADJ] + self.codes self.explanation = "notes insuffisantes" - def record(self, code: str, no_overwrite=False): + def record(self, code: str, no_overwrite=False) -> bool: """Enregistre le code jury pour cette UE. Si no_overwrite, n'enregistre pas s'il y a déjà un code. + Return: True si code enregistré (modifié) """ if code and not code in self.codes: raise ScoValueError( @@ -1213,7 +1238,7 @@ class DecisionsProposeesUE(DecisionsProposees): ) if code == self.code_valide or (self.code_valide is not None and no_overwrite): self.recorded = True - return # no change + return False # no change self.erase() if code is None: self.validation = None @@ -1244,6 +1269,7 @@ class DecisionsProposeesUE(DecisionsProposees): sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id) self.code_valide = code # mise à jour self.recorded = True + return True def erase(self): """Efface la décision de jury de cet étudiant pour cette UE""" diff --git a/app/but/jury_but_validation_auto.py b/app/but/jury_but_validation_auto.py index d5e308ab9..674389b54 100644 --- a/app/but/jury_but_validation_auto.py +++ b/app/but/jury_but_validation_auto.py @@ -18,29 +18,29 @@ from app.scodoc.sco_exceptions import ScoValueError def formsemestre_validation_auto_but( formsemestre: FormSemestre, only_adm: bool = True, no_overwrite: bool = True ) -> int: - """Calcul automatique des décisions de jury sur une année BUT. - Ne modifie jamais de décisions de l'année scolaire précédente, même + """Calcul automatique des décisions de jury sur une "année" BUT. + + - N'enregistre jamais de décisions de l'année scolaire précédente, même si on a des RCUE "à cheval". - Normalement, only_adm est True et on n'enregistre que les décisions ADM (de droit). - Si only_adm est faux, on enregistre la première décision proposée par ScoDoc - (mode à n'utiliser que pour les tests) + - Ne modifie jamais de décisions déjà enregistrées (sauf si no_overwrite est faux, + ce qui est utilisé pour certains tests unitaires). + - Normalement, only_adm est True et on n'enregistre que les décisions validantes + de droit: ADM ou CMP. + En revanche, si only_adm est faux, on enregistre la première décision proposée par ScoDoc + (mode à n'utiliser que pour les tests unitaires vérifiant la saisie des jurys) - Si no_overwrite est vrai (défaut), ne ré-écrit jamais les codes déjà enregistrés - (utiliser faux pour certains tests) - - Returns: nombre d'étudiants "admis" + Returns: nombre d'étudiants pour lesquels on a enregistré au moins un code. """ if not formsemestre.formation.is_apc(): raise ScoValueError("fonction réservée aux formations BUT") - nb_admis = 0 + nb_etud_modif = 0 with sco_cache.DeferredSemCacheManager(): for etudid in formsemestre.etuds_inscriptions: etud: Identite = Identite.query.get(etudid) deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre) - if deca.admis: # année réussie - nb_admis += 1 - if deca.admis or not only_adm: - deca.record_all(no_overwrite=no_overwrite) + nb_etud_modif += deca.record_all( + no_overwrite=no_overwrite, only_validantes=only_adm + ) db.session.commit() - return nb_admis + return nb_etud_modif diff --git a/app/pe/pe_jurype.py b/app/pe/pe_jurype.py index 7b0c79970..4aed6fc6d 100644 --- a/app/pe/pe_jurype.py +++ b/app/pe/pe_jurype.py @@ -563,9 +563,8 @@ class JuryPE(object): dec = nt.get_etud_decision_sem( etudid ) # quelle est la décision du jury ? - if dec and dec["code"] in list( - sco_codes_parcours.CODES_SEM_VALIDES.keys() - ): # isinstance( sesMoyennes[i+1], float) and + if dec and (dec["code"] in sco_codes_parcours.CODES_SEM_VALIDES): + # isinstance( sesMoyennes[i+1], float) and # mT = sesMoyennes[i+1] # substitue la moyenne si le semestre suivant est "valide" leFid = sem["formsemestre_id"] else: diff --git a/app/scodoc/sco_codes_parcours.py b/app/scodoc/sco_codes_parcours.py index bbaa61b8f..df5484fb0 100644 --- a/app/scodoc/sco_codes_parcours.py +++ b/app/scodoc/sco_codes_parcours.py @@ -187,20 +187,23 @@ CODES_EXPL = { # Les codes de semestres: CODES_JURY_SEM = {ADC, ADJ, ADM, AJ, ATB, ATJ, ATT, DEF, NAR, RAT} -CODES_SEM_VALIDES = {ADM: True, ADC: True, ADJ: True} # semestre validé -CODES_SEM_ATTENTES = {ATT: True, ATB: True, ATJ: True} # semestre en attente +CODES_SEM_VALIDES_DE_DROIT = {ADM, ADC} +CODES_SEM_VALIDES = CODES_SEM_VALIDES_DE_DROIT | {ADJ} # semestre validé +CODES_SEM_ATTENTES = {ATT, ATB, ATJ} # semestre en attente -CODES_SEM_REO = {NAR: 1} # reorientation +CODES_SEM_REO = {NAR} # reorientation -CODES_UE_VALIDES = {ADM: True, CMP: True, ADJ: True, ADJR: True} +CODES_UE_VALIDES_DE_DROIT = {ADM, CMP} # validation "de droit" +CODES_UE_VALIDES = CODES_UE_VALIDES_DE_DROIT | {ADJ, ADJR} "UE validée" -CODES_RCUE_VALIDES = {ADM, CMP, ADJ} +CODES_RCUE_VALIDES_DE_DROIT = {ADM, CMP} +CODES_RCUE_VALIDES = CODES_RCUE_VALIDES_DE_DROIT | {ADJ} "Niveau RCUE validé" # Pour le BUT: +CODES_ANNEE_BUT_VALIDES_DE_DROIT = {ADM, PASD} CODES_ANNEE_ARRET = {DEF, DEM, ABAN, ABL} -CODES_RCUE = {ADM, AJ, CMP} BUT_BARRE_UE8 = 8.0 - NOTES_TOLERANCE BUT_BARRE_UE = BUT_BARRE_RCUE = 10.0 - NOTES_TOLERANCE BUT_RCUE_SUFFISANT = 8.0 - NOTES_TOLERANCE @@ -230,17 +233,17 @@ BUT_CODES_ORDERED = { def code_semestre_validant(code: str) -> bool: "Vrai si ce CODE entraine la validation du semestre" - return CODES_SEM_VALIDES.get(code, False) + return code in CODES_SEM_VALIDES def code_semestre_attente(code: str) -> bool: "Vrai si ce CODE est un code d'attente (semestre validable plus tard par jury ou compensation)" - return CODES_SEM_ATTENTES.get(code, False) + return code in CODES_SEM_ATTENTES def code_ue_validant(code: str) -> bool: "Vrai si ce code d'UE est validant (ie attribue les ECTS)" - return CODES_UE_VALIDES.get(code, False) + return code in CODES_UE_VALIDES DEVENIR_EXPL = { diff --git a/app/scodoc/sco_cursus_dut.py b/app/scodoc/sco_cursus_dut.py index 3474081ff..0549f6677 100644 --- a/app/scodoc/sco_cursus_dut.py +++ b/app/scodoc/sco_cursus_dut.py @@ -890,7 +890,7 @@ def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite) car ils ne dépendent que de la note d'UE et de la validation ou non du semestre. Les UE des semestres NON ASSIDUS ne sont jamais validées (code AJ). """ - valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False) + valid_semestre = code_etat_sem in CODES_SEM_VALIDES cnx = ndb.GetDBConnexion() formsemestre = FormSemestre.query.get_or_404(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index 2af4b69f3..25b47df3f 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -781,8 +781,8 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None ) # Choix code semestre: - codes = list(sco_codes_parcours.CODES_JURY_SEM) - codes.sort() # fortuitement, cet ordre convient bien ! + codes = sorted(sco_codes_parcours.CODES_JURY_SEM) + # fortuitement, cet ordre convient bien ! H.append( 'Code semestre: