diff --git a/app/comp/moy_ue.py b/app/comp/moy_ue.py index 8c1d68dab..c533c21aa 100644 --- a/app/comp/moy_ue.py +++ b/app/comp/moy_ue.py @@ -33,10 +33,7 @@ import pandas as pd from app import db from app import models from app.models import ( - DispenseUE, FormSemestre, - FormSemestreInscription, - Identite, Module, ModuleImpl, ModuleUECoef, @@ -218,31 +215,6 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple: ) -def load_dispense_ues( - formsemestre: FormSemestre, etudids: pd.Index, ues: list[UniteEns] -) -> set[tuple[int, int]]: - """Construit l'ensemble des - etudids = modimpl_inscr_df.index, # les etudids - ue_ids : modimpl_coefs_df.index, # les UE du formsemestre sans les UE bonus sport - - Résultat: set de (etudid, ue_id). - """ - dispense_ues = set() - ue_sem_by_code = {ue.ue_code: ue for ue in ues} - # Prend toutes les dispenses obtenues par des étudiants de ce formsemestre, - # puis filtre sur inscrits et code d'UE UE - for dispense_ue in DispenseUE.query.join( - Identite, FormSemestreInscription - ).filter_by(formsemestre_id=formsemestre.id): - if dispense_ue.etudid in etudids: - # UE dans le semestre avec même code ? - ue = ue_sem_by_code.get(dispense_ue.ue.ue_code) - if ue is not None: - dispense_ues.add((dispense_ue.etudid, ue.id)) - - return dispense_ues - - def compute_ue_moys_apc( sem_cube: np.array, etuds: list, diff --git a/app/comp/res_but.py b/app/comp/res_but.py index 28d8cf72d..f3f0c97db 100644 --- a/app/comp/res_but.py +++ b/app/comp/res_but.py @@ -16,7 +16,7 @@ from app.comp.res_compat import NotesTableCompat from app.comp.bonus_spo import BonusSport from app.models import ScoDocSiteConfig from app.models.moduleimpls import ModuleImpl -from app.models.ues import UniteEns +from app.models.ues import DispenseUE, UniteEns from app.scodoc.sco_codes_parcours import UE_SPORT from app.scodoc import sco_preferences @@ -72,7 +72,7 @@ class ResultatsSemestreBUT(NotesTableCompat): modimpl.module.ue.type != UE_SPORT for modimpl in self.formsemestre.modimpls_sorted ] - self.dispense_ues = moy_ue.load_dispense_ues( + self.dispense_ues = DispenseUE.load_formsemestre_dispense_ues_set( self.formsemestre, self.modimpl_inscr_df.index, self.ues ) self.etud_moy_ue = moy_ue.compute_ue_moys_apc( diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index e985a153e..6654d691f 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -59,9 +59,8 @@ class FormSemestre(db.Model): titre = db.Column(db.Text(), nullable=False) date_debut = db.Column(db.Date(), nullable=False) date_fin = db.Column(db.Date(), nullable=False) - etat = db.Column( - db.Boolean(), nullable=False, default=True, server_default="true" - ) # False si verrouillé + etat = db.Column(db.Boolean(), nullable=False, default=True, server_default="true") + "False si verrouillé" modalite = db.Column( db.String(SHORT_STR_LEN), db.ForeignKey("notes_form_modalites.modalite") ) # "FI", "FAP", "FC", ... diff --git a/app/models/ues.py b/app/models/ues.py index b56f209c5..387fc8d28 100644 --- a/app/models/ues.py +++ b/app/models/ues.py @@ -1,6 +1,8 @@ """ScoDoc 9 models : Unités d'Enseignement (UE) """ +import pandas as pd + from app import db, log from app.models import APO_CODE_STR_LEN from app.models import SHORT_STR_LEN @@ -249,12 +251,23 @@ class UniteEns(db.Model): class DispenseUE(db.Model): """Dispense d'UE - Utilisé en PCC (BUT) pour indiquer les étudiants redoublants avec une UE capitalisée + Utilisé en APC (BUT) pour indiquer les étudiants redoublants avec une UE capitalisée qu'ils ne refont pas. + La dispense d'UE n'est PAS une validation: + - elle n'est pas affectée par les décisions de jury (pas effacée) + - elle est associée à un formsemestre + - elle ne permet pas la délivrance d'ECTS ou du diplôme. + + On utilise cette dispense et non une "inscription" par souci d'efficacité: + en général, la grande majorité des étudiants suivront toutes les UEs de leur parcours, + la dispense étant une exception. """ - __table_args__ = (db.UniqueConstraint("ue_id", "etudid"),) + __table_args__ = (db.UniqueConstraint("formsemestre_id", "ue_id", "etudid"),) id = db.Column(db.Integer, primary_key=True) + formsemestre_id = formsemestre_id = db.Column( + db.Integer, db.ForeignKey("notes_formsemestre.id"), index=True, nullable=True + ) ue_id = db.Column( db.Integer, db.ForeignKey(UniteEns.id, ondelete="CASCADE"), @@ -273,3 +286,25 @@ class DispenseUE(db.Model): def __repr__(self) -> str: return f"""<{self.__class__.__name__} {self.id} etud={ repr(self.etud)} ue={repr(self.ue)}>""" + + @classmethod + def load_formsemestre_dispense_ues_set( + cls, formsemestre: "FormSemestre", etudids: pd.Index, ues: list[UniteEns] + ) -> set[tuple[int, int]]: + """Construit l'ensemble des + etudids = modimpl_inscr_df.index, # les etudids + ue_ids : modimpl_coefs_df.index, # les UE du formsemestre sans les UE bonus sport + + Résultat: set de (etudid, ue_id). + """ + # Prend toutes les dispenses obtenues par des étudiants de ce formsemestre, + # puis filtre sur inscrits et ues + ue_ids = {ue.id for ue in ues} + dispense_ues = { + (dispense_ue.etudid, dispense_ue.ue_id) + for dispense_ue in DispenseUE.query.filter_by( + formsemestre_id=formsemestre.id + ) + if dispense_ue.etudid in etudids and dispense_ue.ue_id in ue_ids + } + return dispense_ues diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index 44f14954a..af37125fc 100644 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -1088,9 +1088,10 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True): """cliquez sur un module pour saisir des notes""" ) elif datetime.date.today() > formsemestre.date_fin: - H.append( - """semestre terminé""" - ) + if formsemestre.etat: + H.append( + """semestre du passé non verrouillé""" + ) else: H.append( """semestre pas encore commencé""" diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py index 115337b2f..101de24d9 100644 --- a/app/scodoc/sco_moduleimpl_inscriptions.py +++ b/app/scodoc/sco_moduleimpl_inscriptions.py @@ -35,15 +35,13 @@ from flask_login import current_user from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre, Identite, UniteEns +from app.models import FormSemestre, Identite, ScolarFormSemestreValidation, UniteEns -import app.scodoc.notesdb as ndb -import app.scodoc.sco_utils as scu from app import log from app.scodoc.scolog import logdb from app.scodoc import html_sco_header from app.scodoc import htmlutils -from app.scodoc import sco_cache +from app.scodoc import sco_codes_parcours from app.scodoc import sco_edit_module from app.scodoc import sco_edit_ue from app.scodoc import sco_etud @@ -51,8 +49,10 @@ from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl +import app.scodoc.notesdb as ndb from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_permissions import Permission +import app.scodoc.sco_utils as scu def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False): @@ -527,14 +527,39 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) -> >{etud.nomprenom}""" ) for ue in ues: + td_class = "" est_inscr = ues_etud.get(ue.id) # None si pas concerné if est_inscr is None: content = "" else: + # Validations d'UE déjà enregistrées dans d'autres semestres + validations_ue = ( + ScolarFormSemestreValidation.query.filter_by(etudid=etudid) + .filter( + ScolarFormSemestreValidation.formsemestre_id + != res.formsemestre.id, + ScolarFormSemestreValidation.code.in_( + sco_codes_parcours.CODES_UE_VALIDES + ), + ) + .join(UniteEns) + .filter_by(ue_code=ue.ue_code) + .all() + ) + validations_ue.sort( + key=lambda v: sco_codes_parcours.BUT_CODES_ORDERED.get(v.code, 0) + ) + validation = validations_ue[-1] if validations_ue else None + expl_validation = ( + f"""Validée ({validation.code}) le {validation.event_date.strftime("%d/%m/%Y")}""" + if validation + else "" + ) + td_class = ' class="ue_validee"' if validation else "" content = f""" /> """ - H.append(f"""