From c833974569dc8046abd5335595d07e3a8b7c0f4c Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 11 Jan 2023 09:37:02 -0300 Subject: [PATCH] =?UTF-8?q?Jury=20BUT:=20am=C3=A9lioration=20front=20et=20?= =?UTF-8?q?back.=20Voir=20#547.=20Tests=20YAML:=20refonte=20circuit=20jury?= =?UTF-8?q?.=20Cas=20lyon43.=20Tests=20ok.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/but/jury_but.py | 97 ++++-- app/but/jury_but_view.py | 82 +++-- app/models/but_validations.py | 3 +- app/models/validations.py | 4 + app/scodoc/sco_formsemestre.py | 2 + app/scodoc/sco_formsemestre_edit.py | 1 + app/static/css/jury_but.css | 2 + app/static/js/jury_but.js | 31 +- tests/unit/cursus_but_geii_lyon.yaml | 483 ++++++++++++++++----------- tests/unit/sco_fake_gen.py | 3 +- tests/unit/test_but_jury.py | 26 +- tests/unit/yaml_setup.py | 2 +- 12 files changed, 459 insertions(+), 277 deletions(-) diff --git a/app/but/jury_but.py b/app/but/jury_but.py index d2d1039b2a..f8611914da 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -584,9 +584,10 @@ class DecisionsProposeesAnnee(DecisionsProposees): def compute_decisions_niveaux(self) -> dict[int, "DecisionsProposeesRCUE"]: """Pour chaque niveau de compétence de cette année, construit - le DecisionsProposeesRCUE, - ou None s'il n'y en a pas + le DecisionsProposeesRCUE, ou None s'il n'y en a pas (ne devrait pas arriver car compute_rcues_annee vérifie déjà cela). + + Appelé à la construction du deca, donc avant décisions manuelles. Return: { niveau_id : DecisionsProposeesRCUE } """ # Retrouve le RCUE associé à chaque niveau @@ -633,6 +634,7 @@ class DecisionsProposeesAnnee(DecisionsProposees): def record_form(self, form: dict): """Enregistre les codes de jury en base + à partir d'un dict représentant le formulaire jury BUT: form dict: - 'code_ue_1896' : 'AJ' code pour l'UE id 1896 - 'code_rcue_6" : 'ADM' code pour le RCUE du niveau 6 @@ -642,31 +644,41 @@ class DecisionsProposeesAnnee(DecisionsProposees): et qu'il n'y en a pas déjà, enregistre ceux par défaut. """ log("jury_but.DecisionsProposeesAnnee.record_form") - with sco_cache.DeferredSemCacheManager(): - for key in form: - code = form[key] - # Codes d'UE - m = re.match(r"^code_ue_(\d+)$", key) + code_annee = None + codes_rcues = [] # [ (dec_rcue, code), ... ] + codes_ues = [] # [ (dec_ue, code), ... ] + for key in form: + code = form[key] + # Codes d'UE + m = re.match(r"^code_ue_(\d+)$", key) + if m: + ue_id = int(m.group(1)) + dec_ue = self.decisions_ues.get(ue_id) + if not dec_ue: + raise ScoValueError(f"UE invalide ue_id={ue_id}") + codes_ues.append((dec_ue, code)) + else: + # Codes de RCUE + m = re.match(r"^code_rcue_(\d+)$", key) if m: - ue_id = int(m.group(1)) - dec_ue = self.decisions_ues.get(ue_id) - if not dec_ue: - raise ScoValueError(f"UE invalide ue_id={ue_id}") - dec_ue.record(code) - else: - # Codes de RCUE - m = re.match(r"^code_rcue_(\d+)$", key) - if m: - niveau_id = int(m.group(1)) - dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id) - if not dec_rcue: - raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}") - dec_rcue.record(code) - elif key == "code_annee": - # Code annuel - self.record(code) + niveau_id = int(m.group(1)) + dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id) + if not dec_rcue: + raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}") + codes_rcues.append((dec_rcue, code)) + elif key == "code_annee": + # Code annuel + code_annee = code + + with sco_cache.DeferredSemCacheManager(): + # Enregistre les codes, dans l'ordre UE, RCUE, Année + for dec_ue, code in codes_ues: + dec_ue.record(code) + for dec_rcue, code in codes_rcues: + dec_rcue.record(code) + self.record(code_annee) + self.record_all() - self.record_all() db.session.commit() def record(self, code: str, no_overwrite=False): @@ -790,6 +802,15 @@ class DecisionsProposeesAnnee(DecisionsProposees): msg=f"Validation année BUT{self.annee_but}: effacée", ) db.session.delete(validation) + # Efface éventuelle validation de semestre + # (en principe inutilisées en BUT) + # et autres UEs (en cas de changement d'architecture de formation depuis le jury ?) + # + for validation in ScolarFormSemestreValidation.query.filter_by( + etudid=self.etud.id, formsemestre_id=self.formsemestre_id + ): + db.session.delete(validation) + db.session.flush() self.invalidate_formsemestre_cache() @@ -878,6 +899,7 @@ class DecisionsProposeesRCUE(DecisionsProposees): inscription_etat: str = scu.INSCRIT, ): super().__init__(etud=dec_prop_annee.etud) + self.deca = dec_prop_annee self.rcue = rcue if rcue is None: # RCUE non dispo, eg un seul semestre self.codes = [] @@ -928,7 +950,11 @@ class DecisionsProposeesRCUE(DecisionsProposees): } codes={self.codes} explanation={self.explanation}""" def record(self, code: str, no_overwrite=False): - """Enregistre le code""" + """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 if self.inscription_etat != scu.INSCRIT: @@ -964,6 +990,15 @@ class DecisionsProposeesRCUE(DecisionsProposees): msg=f"Validation {self.rcue}: {code}", ) db.session.add(self.validation) + # Modifie au besoin les codes d'UE + if code == "ADJ": + deca = self.deca + for ue_id in (self.rcue.ue_1.id, self.rcue.ue_2.id): + dec_ue = deca.decisions_ues.get(ue_id) + if dec_ue and dec_ue.code_valide not in CODES_UE_VALIDES: + log(f"rcue.record: force ADJ sur {dec_ue}") + dec_ue.record("ADJ") + if self.rcue.formsemestre_1 is not None: sco_cache.invalidate_formsemestre( formsemestre_id=self.rcue.formsemestre_1.id @@ -972,6 +1007,7 @@ class DecisionsProposeesRCUE(DecisionsProposees): sco_cache.invalidate_formsemestre( formsemestre_id=self.rcue.formsemestre_2.id ) + self.code_valide = code # mise à jour état self.recorded = True def erase(self): @@ -1032,14 +1068,14 @@ class DecisionsProposeesUE(DecisionsProposees): ): # Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non) # mais ici on a restreint au formsemestre donc une seule (prend la première) - self.validation = ScolarFormSemestreValidation.query.filter_by( + validation = ScolarFormSemestreValidation.query.filter_by( etudid=etud.id, formsemestre_id=formsemestre.id, ue_id=ue.id ).first() super().__init__( etud=etud, - code_valide=self.validation.code if self.validation is not None else None, + code_valide=validation.code if validation is not None else None, ) - # log(f"built {self}") + self.validation = validation self.formsemestre = formsemestre self.ue: UniteEns = ue self.rcue: RegroupementCoherentUE = None @@ -1082,7 +1118,7 @@ class DecisionsProposeesUE(DecisionsProposees): def set_rcue(self, rcue: RegroupementCoherentUE): """Rattache cette UE à un RCUE. Cela peut modifier les codes - proposés (si compensation)""" + proposés par compute_codes() (si compensation)""" self.rcue = rcue def compute_codes(self): @@ -1138,6 +1174,7 @@ class DecisionsProposeesUE(DecisionsProposees): log(f"DecisionsProposeesUE: recording {self.validation}") sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id) + self.code_valide = code # mise à jour self.recorded = True def erase(self): diff --git a/app/but/jury_but_view.py b/app/but/jury_but_view.py index d08329ea47..3140bd02e2 100644 --- a/app/but/jury_but_view.py +++ b/app/but/jury_but_view.py @@ -30,6 +30,7 @@ from app.models import ( Identite, UniteEns, ScolarAutorisationInscription, + ScolarFormSemestreValidation, ) from app.scodoc import html_sco_header from app.scodoc.sco_exceptions import ScoValueError @@ -50,7 +51,7 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: _gen_but_select("code_annee", deca.codes, deca.code_valide, disabled=True, klass="manual") } - ({'non ' if deca.code_valide is None else ''}enregistrée) + ({deca.code_valide or 'non'} enregistrée) """ ) @@ -108,8 +109,8 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: if ue.niveau_competence and ue.niveau_competence.id == niveau.id ] ue_pair = ues[0] if ues else None - # Les UEs à afficher, toujours en readonly - # sur le formsemestre de l'année précédente du redoublant + # Les UEs à afficher, + # qui seront toujours en readonly sur le formsemestre de l'année précédente du redoublant ues_ro = [ ( ue_impair, @@ -132,6 +133,7 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: deca.decisions_ues[ue.id], disabled=read_only or ue_read_only, annee_prec=ue_read_only, + niveau_id=ue.niveau_competence.id, ) ) else: @@ -150,6 +152,7 @@ def _gen_but_select( code_valide: str, disabled: bool = False, klass: str = "", + data: dict = {}, ) -> str: "Le menu html select avec les codes" # if disabled: # mauvaise idée car le disabled est traité en JS @@ -165,8 +168,11 @@ def _gen_but_select( ) return f""" """ @@ -176,6 +182,7 @@ def _gen_but_niveau_ue( dec_ue: DecisionsProposeesUE, disabled: bool = False, annee_prec: bool = False, + niveau_id: int = None, ) -> str: if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]: moy_ue_str = f"""{ @@ -196,7 +203,14 @@ def _gen_but_niveau_ue( """ else: moy_ue_str = f"""{scu.fmt_note(dec_ue.moy_ue)}""" - scoplement = "" + if dec_ue.code_valide: + scoplement = f"""
+ Code {dec_ue.code_valide} enregistré le {dec_ue.validation.event_date.strftime("%d/%m/%Y")} + à {dec_ue.validation.event_date.strftime("%Hh%M")} +
+ """ + else: + scoplement = "" return f"""
{ _gen_but_select("code_ue_"+str(ue.id), - dec_ue.codes, - dec_ue.code_valide, disabled=disabled + dec_ue.codes, + dec_ue.code_valide, + disabled=disabled, + klass=f"code_ue ue_rcue_{niveau_id}" if not disabled else "" ) }
@@ -250,12 +266,13 @@ def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str: {scoplement}
-
{_gen_but_select("code_rcue_"+str(niveau.id), + {_gen_but_select("code_rcue_"+str(niveau.id), dec_rcue.codes, dec_rcue.code_valide, - disabled=True, klass="manual" + disabled=True, + klass="manual code_rcue", + data = { "niveau_id" : str(niveau.id)} )} -
""" @@ -274,17 +291,15 @@ def jury_but_semestriel( semestre_terminal = ( formsemestre.semestre_id >= formsemestre.formation.get_parcours().NB_SEM ) + autorisations_passage = ScolarAutorisationInscription.query.filter_by( + etudid=etud.id, + origin_formsemestre_id=formsemestre.id, + ).all() # Par défaut: autorisé à passer dans le semestre suivant si sem. impair, # ou si décision déjà enregistrée: est_autorise_a_passer = (formsemestre.semestre_id % 2) or ( formsemestre.semestre_id + 1 - ) in ( - a.semestre_id - for a in ScolarAutorisationInscription.query.filter_by( - etudid=etud.id, - origin_formsemestre_id=formsemestre.id, - ) - ) + ) in (a.semestre_id for a in autorisations_passage) decisions_ues = { ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat) for ue in ues @@ -308,7 +323,9 @@ def jury_but_semestriel( flash("codes enregistrés") if not semestre_terminal: if request.form.get("autorisation_passage"): - if not est_autorise_a_passer: + if not formsemestre.semestre_id + 1 in ( + a.semestre_id for a in autorisations_passage + ): ScolarAutorisationInscription.autorise_etud( etud.id, formsemestre.formation.formation_code, @@ -368,21 +385,31 @@ def jury_but_semestriel( }">{etud.photo_html(title="fiche de " + etud.nomprenom)} -

Jury sur un semestre BUT isolé

+

Jury sur un semestre BUT isolé (ne concerne que les UEs)

{warning}
""", ] - if (not read_only) and any([dec.code_valide for dec in decisions_ues.values()]): - erase_span = f"""effacer les décisions enregistrées""" - else: - erase_span = "Cet étudiant n'a aucune décision enregistrée pour ce semestre." + + erase_span = "" + if not read_only: + # Requête toutes les validations (pas seulement celles du deca courant), + # au cas où: changement d'architecture, saisie en mode classique, ... + validations = ScolarFormSemestreValidation.query.filter_by( + etudid=etud.id, formsemestre_id=formsemestre.id + ).all() + if validations: + erase_span = f"""effacer les décisions enregistrées""" + else: + erase_span = ( + "Cet étudiant n'a aucune décision enregistrée pour ce semestre." + ) H.append( f""" @@ -436,6 +463,9 @@ def jury_but_semestriel( autoriser à passer dans le semestre S{formsemestre.semestre_id+1} + {("(autorisations enregistrées: " + ' '.join( + 'S' + str(a.semestre_id or '') for a in autorisations_passage) + ")" + ) if autorisations_passage else ""} """ diff --git a/app/models/but_validations.py b/app/models/but_validations.py index ab820bc386..52826003e6 100644 --- a/app/models/but_validations.py +++ b/app/models/but_validations.py @@ -68,7 +68,8 @@ class ApcValidationRCUE(db.Model): "description en HTML" return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: {self.code} - enregistrée le {self.date.strftime("%d/%m/%Y")}""" + enregistrée le {self.date.strftime("%d/%m/%Y")} + à {self.date.strftime("%Hh%M")}""" def niveau(self) -> ApcNiveau: """Le niveau de compétence associé à cet RCUE.""" diff --git a/app/models/validations.py b/app/models/validations.py index 5998ccd738..aa53251709 100644 --- a/app/models/validations.py +++ b/app/models/validations.py @@ -93,6 +93,10 @@ class ScolarAutorisationInscription(db.Model): db.ForeignKey("notes_formsemestre.id"), ) + def __repr__(self) -> str: + return f"""{self.__class__.__name__}(id={self.id}, etudid={ + self.etudid}, semestre_id={self.semestre_id})""" + def to_dict(self) -> dict: "as a dict" d = dict(self.__dict__) diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index 98451498fe..2edd365564 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -87,6 +87,8 @@ _formsemestreEditor = ndb.EditableTable( "resp_can_edit": bool, "resp_can_change_ens": bool, "ens_can_edit_eval": bool, + "bul_bgcolor": lambda color: color or "white", + "titre": lambda titre: titre or "sans titre", }, ) diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index a6e79b1f40..5a776c35b2 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -312,6 +312,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N le titre: ils seront automatiquement ajoutés """, + "allow_null": False, }, ), ( diff --git a/app/static/css/jury_but.css b/app/static/css/jury_but.css index f865400a04..74a1f5b71e 100644 --- a/app/static/css/jury_but.css +++ b/app/static/css/jury_but.css @@ -157,6 +157,8 @@ div.but_niveau_ue.annee_prec { background-color: rgb(167, 167, 0); } +div.but_section_annee, +div.but_niveau_rcue.modified, div.but_niveau_ue.modified { background-color: rgb(255, 214, 254); } diff --git a/app/static/js/jury_but.js b/app/static/js/jury_but.js index 54d28a841d..b016f110cc 100644 --- a/app/static/js/jury_but.js +++ b/app/static/js/jury_but.js @@ -5,17 +5,36 @@ function enable_manual_codes(elt) { $(".jury_but select.manual").prop("disabled", !elt.checked); } -// changement menu code: +// changement d'un menu code: function change_menu_code(elt) { - elt.parentElement.parentElement.classList.remove("recorded"); - // TODO: comparer avec valeur enregistrée (à mettre en data-orig ?) - // et colorer en fonction - elt.parentElement.parentElement.classList.add("modified"); + // Ajuste styles pour visualiser codes enregistrés/modifiés + if (elt.value != elt.dataset.orig_code) { + elt.parentElement.parentElement.classList.add("modified"); + } else { + elt.parentElement.parentElement.classList.remove("modified"); + } + if (elt.value == elt.dataset.orig_recorded) { + elt.parentElement.parentElement.classList.add("recorded"); + } else { + elt.parentElement.parentElement.classList.remove("recorded"); + } + // Si RCUE passant en ADJ, change les menus des UEs associées + if (elt.classList.contains("code_rcue") + && elt.dataset.niveau_id + && elt.value == "ADJ" + && elt.value != elt.dataset.orig_recorded) { + let ue_selects = elt.parentElement.parentElement.parentElement.querySelectorAll( + "select.ue_rcue_" + elt.dataset.niveau_id); + ue_selects.forEach(select => { + select.value = "ADJ"; + change_menu_code(select); // pour changer les styles + }); + } } $(function () { // Recupère la liste ordonnées des etudids - // pour avoir le "suivant" etr le "précédent" + // pour avoir le "suivant" et le "précédent" // (liens de navigation) const url = new URL(document.URL); const frags = url.pathname.split("/"); // .../formsemestre_validation_but/formsemestre_id/etudid diff --git a/tests/unit/cursus_but_geii_lyon.yaml b/tests/unit/cursus_but_geii_lyon.yaml index e73b7b1db6..4dd216cc6d 100644 --- a/tests/unit/cursus_but_geii_lyon.yaml +++ b/tests/unit/cursus_but_geii_lyon.yaml @@ -1,204 +1,279 @@ -# Tests unitaires jury BUT - IUT Lyon GEII -# Essais avec un BUT GEII, 2 UE en BUT1 / 4 UE en BUT2-BUT3 et 3 parcours -# Contrib Pascal B. - -ReferentielCompetences: - filename: but-GEII-05012022-081639.xml - specialite: GEII - -Formation: - filename: scodoc_formation_BUT_GEII_lyon_v1.xml - # Association des UE aux compétences: - ues: - # S1 : Tronc commun GEII - 'UE11': - annee: BUT1 - competence: Concevoir - 'UE12': - annee: BUT1 - competence: Vérifier - - # S2 : Tronc commun GEII - 'UE21': - annee: BUT1 - competence: Concevoir - 'UE22': - annee: BUT1 - competence: Vérifier - - # S3 : Tronc commun GEII - 'UE31': - annee: BUT2 - competence: Concevoir - 'UE32': - annee: BUT2 - competence: Vérifier - 'UE33': - annee: BUT2 - competence: Maintenir - # S3 : Parcours EME - 'UE34EME': - annee: BUT2 - competence: Installer - parcours: EME - # S3 : Parcours ESE - 'UE34ESE': - annee: BUT2 - competence: Implanter - parcours: ESE - # S3 : Parcours AII - 'UE34AII': - annee: BUT2 - competence: Intégrer - parcours: AII - - # S4 : Tronc commun GEII - 'UE41': - annee: BUT2 - competence: Concevoir - 'UE42': - annee: BUT2 - competence: Vérifier - 'UE43': - annee: BUT2 - competence: Maintenir - # S4 : Parcours EME - 'UE44EME': - annee: BUT2 - competence: Installer - parcours: EME - # S4 : Parcours ESE - 'UE44ESE': - annee: BUT2 - competence: Implanter - parcours: ESE - # S4 : Parcours AII - 'UE44AII': - annee: BUT2 - competence: Intégrer - parcours: AII - - modules_parcours: - # cette section permet d'associer des modules à des parcours - # les codes modules peuvent être des regexp - EME: [ .*EME.* ] - ESE: [ .*ESE.* ] - AII: [ .*AII.* ] - -FormSemestres: - # S1 et S2 : - S1: - idx: 1 - date_debut: 2021-09-01 - date_fin: 2022-01-15 - S2: - idx: 2 - date_debut: 2022-01-16 - date_fin: 2022-06-30 - # S3 avec les trois parcours réunis: - # S3: - # idx: 3 - # date_debut: 2022-09-01 - # date_fin: 2023-01-13 - # codes_parcours: ['AII', 'EME', 'ESE'] - # Un S1 pour les redoublants - S1-red: - idx: 1 - date_debut: 2022-09-02 - date_fin: 2023-01-12 - -Etudiants: - geii8: - prenom: etugeii8 - civilite: M - formsemestres: - S1: - notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" - "S1.1": 7.00 - "S1.2": 9.00 - attendu: # les codes jury que l'on doit vérifier - deca: - passage_de_droit: False - nb_competences: 2 - nb_rcue_annee: 0 - decisions_ues: - "UE11": - codes: [ "AJ", "..." ] - code_valide: AJ - decision_jury: AJ - moy_ue: 7.00 - "UE12": - codes: [ "AJ", "..." ] - code_valide: AJ - decision_jury: AJ - moy_ue: 9.00 - S2: - notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" - "S2.1": 12.00 - "S2.2": 12.00 - attendu: # les codes jury que l'on doit vérifier - deca: - passage_de_droit: False - nb_competences: 2 - nb_rcue_annee: 2 - valide_moitie_rcue: False - codes: [ "RED", "..." ] - decisions_ues: - "UE21": - codes: [ "ADM", "..." ] - code_valide: ADM - decision_jury: ADM - moy_ue: 12.00 - "UE22": - codes: [ "ADM", "..." ] - code_valide: ADM - decision_jury: ADM - moy_ue: 12.00 - decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1) - "UE11": - code_valide: AJ - decision_jury: AJ - rcue: - moy_rcue: 9.50 - est_compensable: False - "UE12": - code_valide: CMP - decision_jury: CMP - rcue: - moy_rcue: 10.50 - est_compensable: True - decision_annee: RED - S1-red: - notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" - "S1.1": 9.50 - "S1.2": 7.00 - attendu: # les codes jury que l'on doit vérifier - deca: - passage_de_droit: False - nb_competences: 2 - nb_rcue_annee: 2 - decisions_ues: - "UE11": - codes: [ "CMP", "..." ] - code_valide: CMP - decision_jury: CMP - moy_ue: 9.50 - "UE12": - codes: [ "AJ", "..." ] - code_valide: AJ - decision_jury: AJ - moy_ue: 7.00 - decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1) - "UE11": - code_valide: CMP - decision_jury: CMP - rcue: - moy_rcue: 10.75 - est_compensable: True - "UE12": - code_valide: CMP # car validé en fin de S2 - rcue: - moy_rcue: 9.50 # la moyenne courante (et non enregistrée), donc pas 10.5 - est_compensable: False - decision_annee: ADM - \ No newline at end of file +# Tests unitaires jury BUT - IUT Lyon GEII +# Essais avec un BUT GEII, 2 UE en BUT1 / 4 UE en BUT2-BUT3 et 3 parcours +# Contrib Pascal B. + +ReferentielCompetences: + filename: but-GEII-05012022-081639.xml + specialite: GEII + +Formation: + filename: scodoc_formation_BUT_GEII_lyon_v1.xml + # Association des UE aux compétences: + ues: + # S1 : Tronc commun GEII + 'UE11': + annee: BUT1 + competence: Concevoir + 'UE12': + annee: BUT1 + competence: Vérifier + + # S2 : Tronc commun GEII + 'UE21': + annee: BUT1 + competence: Concevoir + 'UE22': + annee: BUT1 + competence: Vérifier + + # S3 : Tronc commun GEII + 'UE31': + annee: BUT2 + competence: Concevoir + 'UE32': + annee: BUT2 + competence: Vérifier + 'UE33': + annee: BUT2 + competence: Maintenir + # S3 : Parcours EME + 'UE34EME': + annee: BUT2 + competence: Installer + parcours: EME + # S3 : Parcours ESE + 'UE34ESE': + annee: BUT2 + competence: Implanter + parcours: ESE + # S3 : Parcours AII + 'UE34AII': + annee: BUT2 + competence: Intégrer + parcours: AII + + # S4 : Tronc commun GEII + 'UE41': + annee: BUT2 + competence: Concevoir + 'UE42': + annee: BUT2 + competence: Vérifier + 'UE43': + annee: BUT2 + competence: Maintenir + # S4 : Parcours EME + 'UE44EME': + annee: BUT2 + competence: Installer + parcours: EME + # S4 : Parcours ESE + 'UE44ESE': + annee: BUT2 + competence: Implanter + parcours: ESE + # S4 : Parcours AII + 'UE44AII': + annee: BUT2 + competence: Intégrer + parcours: AII + + modules_parcours: + # cette section permet d'associer des modules à des parcours + # les codes modules peuvent être des regexp + EME: [ .*EME.* ] + ESE: [ .*ESE.* ] + AII: [ .*AII.* ] + +FormSemestres: + # S1 et S2 : + S1: + idx: 1 + date_debut: 2021-09-01 + date_fin: 2022-01-15 + S2: + idx: 2 + date_debut: 2022-01-16 + date_fin: 2022-06-30 + # S3 avec les trois parcours réunis: + # S3: + # idx: 3 + # date_debut: 2022-09-01 + # date_fin: 2023-01-13 + # codes_parcours: ['AII', 'EME', 'ESE'] + # Un S1 pour les redoublants + S1-red: + idx: 1 + date_debut: 2022-09-02 + date_fin: 2023-01-12 + +Etudiants: + geii8: + prenom: etugeii8 + civilite: M + formsemestres: + S1: + notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" + "S1.1": 7.00 + "S1.2": 9.00 + attendu: # les codes jury que l'on doit vérifier + deca: + passage_de_droit: False + nb_competences: 2 + nb_rcue_annee: 0 + decisions_ues: + "UE11": + codes: [ "AJ", "..." ] + code_valide: AJ + decision_jury: AJ + moy_ue: 7.00 + "UE12": + codes: [ "AJ", "..." ] + code_valide: AJ + decision_jury: AJ + moy_ue: 9.00 + S2: + notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" + "S2.1": 12.00 + "S2.2": 12.00 + attendu: # les codes jury que l'on doit vérifier + deca: + passage_de_droit: False + nb_competences: 2 + nb_rcue_annee: 2 + valide_moitie_rcue: False + codes: [ "RED", "..." ] + decisions_ues: + "UE21": + codes: [ "ADM", "..." ] + code_valide: ADM + decision_jury: ADM + moy_ue: 12.00 + "UE22": + codes: [ "ADM", "..." ] + code_valide: ADM + decision_jury: ADM + moy_ue: 12.00 + decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1) + "UE11": + code_valide: AJ + decision_jury: AJ + rcue: + moy_rcue: 9.50 + est_compensable: False + "UE12": + code_valide: CMP + decision_jury: CMP + rcue: + moy_rcue: 10.50 + est_compensable: True + decision_annee: RED + S1-red: + notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" + "S1.1": 9.50 + "S1.2": 7.00 + attendu: # les codes jury que l'on doit vérifier + deca: + passage_de_droit: False + nb_competences: 2 + nb_rcue_annee: 2 + decisions_ues: + "UE11": + codes: [ "CMP", "..." ] + code_valide: CMP + decision_jury: CMP + moy_ue: 9.50 + "UE12": + codes: [ "AJ", "..." ] + code_valide: AJ + decision_jury: AJ + moy_ue: 7.00 + decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1) + "UE11": + code_valide: CMP + decision_jury: CMP + rcue: + moy_rcue: 10.75 + est_compensable: True + "UE12": + code_valide: CMP # car validé en fin de S2 + rcue: + moy_rcue: 9.50 # la moyenne courante (et non enregistrée), donc pas 10.5 + est_compensable: False + decision_annee: ADM + geii43: + prenom: etugeii43 + civilite: M + formsemestres: + S1: + notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" + "S1.1": 9.00 + "S1.2": 9.00 + attendu: # les codes jury que l'on doit vérifier + deca: + passage_de_droit: False + nb_competences: 2 + nb_rcue_annee: 0 + decisions_ues: + "UE11": + codes: [ "AJ", "..." ] + code_valide: AJ + decision_jury: AJ + moy_ue: 9.00 + "UE12": + codes: [ "AJ", "..." ] + code_valide: AJ + decision_jury: AJ + moy_ue: 9.00 + S2: + notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" + "S2.1": 9.00 + "S2.2": 9.00 + attendu: # les codes jury que l'on doit vérifier + deca: + passage_de_droit: False + nb_competences: 2 + nb_rcue_annee: 2 + valide_moitie_rcue: False + codes: [ "RED", "..." ] + decisions_ues: + "UE21": + codes: [ "AJ", "..." ] + code_valide: AJ + moy_ue: 9.00 + "UE22": + codes: [ "AJ", "..." ] + code_valide: AJ # va basculer en ADJ car RCUE en ADJ (mais le test est AVANT !) + moy_ue: 9.00 + decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1) + "UE11": + code_valide: AJ + decision_jury: AJ + rcue: + moy_rcue: 9.00 + est_compensable: False + "UE12": + code_valide: AJ # code par défaut proposé + decision_jury: ADJ # code donné par le jury de S2 + rcue: + moy_rcue: 9.00 + est_compensable: False + decision_annee: RED + S1-red: + notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" + "S1.1": 11.00 + "S1.2": 7.00 + attendu: # les codes jury que l'on doit vérifier + deca: + passage_de_droit: false + nb_competences: 2 + nb_rcue_annee: 0 + decisions_ues: + "UE11": + codes: [ "ADM", "..." ] + code_valide: ADM + moy_ue: 11.00 + "UE12": + code_valide: AJ + moy_ue: 7.00 + decision_annee: AJ diff --git a/tests/unit/sco_fake_gen.py b/tests/unit/sco_fake_gen.py index 0c07a27eeb..e70c434c1e 100644 --- a/tests/unit/sco_fake_gen.py +++ b/tests/unit/sco_fake_gen.py @@ -232,7 +232,7 @@ class ScoFake(object): self, formation_id=None, semestre_id=None, - titre=None, + titre="", date_debut=None, date_fin=None, etat=None, @@ -253,6 +253,7 @@ class ScoFake(object): ) -> int: if responsables is None: responsables = (self.default_user.id,) + titre = titre or "sans titre" oid = sco_formsemestre.do_formsemestre_create(locals()) oids = sco_formsemestre.do_formsemestre_list( args={"formsemestre_id": oid} diff --git a/tests/unit/test_but_jury.py b/tests/unit/test_but_jury.py index fa75ae3291..29cf718265 100644 --- a/tests/unit/test_but_jury.py +++ b/tests/unit/test_but_jury.py @@ -1,19 +1,29 @@ +############################################################################## +# ScoDoc +# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved. +# See LICENSE +############################################################################## + """ Test jury BUT avec parcours + +Ces tests sont généralement lents (construction de la base), +et donc marqués par `@pytest.mark.slow`. + +Certains sont aussi marqués par @pytest.mark.lemans ou @pytest.mark.lyon +pour lancer certains tests spécifiques seulement. + +Exemple utilisation spécifique: +# test sur "Lyon" seulement: +pytest --pdb -m lyon tests/unit/test_but_jury.py + """ import pytest from tests.unit import yaml_setup import app -from app.but.jury_but import DecisionsProposeesAnnee from app.but.jury_but_validation_auto import formsemestre_validation_auto_but -from app.models import ( - Formation, - FormSemestre, - Identite, - UniteEns, -) -from app.scodoc import sco_utils as scu +from app.models import FormSemestre from config import TestConfig DEPT = TestConfig.DEPT_TEST diff --git a/tests/unit/yaml_setup.py b/tests/unit/yaml_setup.py index 089b146197..de495dccce 100644 --- a/tests/unit/yaml_setup.py +++ b/tests/unit/yaml_setup.py @@ -34,7 +34,7 @@ formsemestre_validation_auto_but(only_adm=False) test_but_jury() - compare décisions attendues indiquées dans le YAML avec celles de ScoDoc - et enregistre immédiatement après la décision manuelle indiquée par `decision_jury` + et enregistre immédiatement APRES la décision manuelle indiquée par `decision_jury` dans le YAML.