diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 09b08b2fb..185e18a4f 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -36,7 +36,7 @@ from app.models.config import ScoDocSiteConfig from app.models.departements import Departement from app.models.etudiants import Identite from app.models.evaluations import Evaluation -from app.models.events import ScolarNews +from app.models.events import Scolog, ScolarNews from app.models.formations import Formation from app.models.groups import GroupDescr, Partition from app.models.moduleimpls import ( @@ -45,9 +45,10 @@ from app.models.moduleimpls import ( notes_modules_enseignants, ) from app.models.modules import Module +from app.models.scolar_event import ScolarEvent from app.models.ues import UniteEns from app.models.validations import ScolarFormSemestreValidation -from app.scodoc import codes_cursus, sco_preferences +from app.scodoc import codes_cursus, sco_cache, sco_preferences from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_permissions import Permission from app.scodoc.sco_utils import MONTH_NAMES_ABBREV, translate_assiduites_metric @@ -69,6 +70,8 @@ class FormSemestre(models.ScoDocModel): formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id")) semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1") titre = db.Column(db.Text(), nullable=False) + # nb max d'inscriptions (non DEM), null si illimité: + capacite_accueil = db.Column(db.Integer, nullable=True) date_debut = db.Column(db.Date(), nullable=False) date_fin = db.Column(db.Date(), nullable=False) # jour inclus edt_id: str | None = db.Column(db.Text(), index=True, nullable=True) @@ -1019,20 +1022,74 @@ class FormSemestre(models.ScoDocModel): codes |= {x.strip() for x in self.elt_passage_apo.split(",") if x} return codes - def get_inscrits(self, include_demdef=False, order=False) -> list[Identite]: + def get_inscrits( + self, include_demdef=False, order=False, etats: set | None = None + ) -> list[Identite]: """Liste des étudiants inscrits à ce semestre Si include_demdef, tous les étudiants, avec les démissionnaires et défaillants. + Si etats, seuls les étudiants dans l'un des états indiqués. Si order, tri par clé sort_key """ if include_demdef: etuds = [ins.etud for ins in self.inscriptions] - else: + elif not etats: etuds = [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT] + else: + etuds = [ins.etud for ins in self.inscriptions if ins.etat in etats] if order: etuds.sort(key=lambda e: e.sort_key) return etuds + def inscrit_etudiant( + self, + etud: "Identite", + etat: str = scu.INSCRIT, + etape: str | None = None, + method: str | None = None, + ) -> "FormSemestreInscription": + """Inscrit l'étudiant au semestre, ou renvoie son inscription s'il l'est déjà. + Vérifie la capacité d'accueil si indiquée (non null): si le semestre est plein, + lève une exception. Génère un évènement et un log étudiant. + method: indique origine de l'inscription pour le log étudiant. + """ + # remplace ancien do_formsemestre_inscription_create() + if not self.etat: # check lock + raise ScoValueError("inscrit_etudiant: semestre verrouille") + inscr = FormSemestreInscription.query.filter_by( + formsemestre_id=self.id, etudid=etud.id + ).first() + if inscr is not None: + return inscr + + if self.capacite_accueil is not None: + inscriptions = self.get_inscrits(etats={scu.INSCRIT, scu.DEMISSION}) + if len(inscriptions) >= self.capacite_accueil: + raise ScoValueError( + f"Semestre {self.titre} complet: {len(self.inscriptions)} inscrits" + ) + + inscr = FormSemestreInscription( + formsemestre_id=self.id, etudid=etud.id, etat=etat, etape=etape + ) + db.session.add(inscr) + # Évènement + event = ScolarEvent( + etudid=etud.id, + formsemestre_id=self.id, + event_type="INSCRIPTION", + ) + db.session.add(event) + # Log etudiant + Scolog.logdb( + method=method, + etudid=etud.id, + msg=f"inscription en semestre {self.titre_annee()}", + commit=True, + ) + sco_cache.invalidate_formsemestre(formsemestre_id=self.id) + return inscr + def get_partitions_list( self, with_default=True, only_listed=False ) -> list[Partition]: diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index 00ea831e3..5131947ff 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -53,6 +53,7 @@ _formsemestreEditor = ndb.EditableTable( "semestre_id", "formation_id", "titre", + "capacite_accueil", "date_debut", "date_fin", "gestion_compensation", diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index c4cb7b84a..87cfadd00 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -350,8 +350,6 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N "labels": modalites_titles, }, ), - ] - modform.append( ( "semestre_id", { @@ -367,10 +365,21 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N "attributes": ['onchange="change_semestre_id();"'] if is_apc else "", }, ), - ) + ( + "capacite_accueil", + { + "title": "Capacité d'accueil", + "size": 4, + "explanation": "laisser vide si pas de limite au nombre d'inscrits non démissionnaires", + "type": "int", + "allow_null": True, + }, + ), + ] etapes = sco_portal_apogee.get_etapes_apogee_dept() # Propose les etapes renvoyées par le portail - # et ajoute les étapes du semestre qui ne sont pas dans la liste (soit la liste a changé, soit l'étape a été ajoutée manuellement) + # et ajoute les étapes du semestre qui ne sont pas dans la liste + # (soit la liste a changé, soit l'étape a été ajoutée manuellement) etapes_set = {et[0] for et in etapes} if edit: for etape_vdi in formsemestre.etapes_apo_vdi(): diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py index ec4bb9303..e7f0ef9c8 100644 --- a/app/scodoc/sco_formsemestre_inscriptions.py +++ b/app/scodoc/sco_formsemestre_inscriptions.py @@ -85,43 +85,6 @@ def do_formsemestre_inscription_listinscrits(formsemestre_id): return r -def do_formsemestre_inscription_create(args, method=None): - "create a formsemestre_inscription (and sco event)" - cnx = ndb.GetDBConnexion() - log(f"do_formsemestre_inscription_create: args={args}") - sems = sco_formsemestre.do_formsemestre_list( - {"formsemestre_id": args["formsemestre_id"]} - ) - if len(sems) != 1: - raise ScoValueError(f"code de semestre invalide: {args['formsemestre_id']}") - sem = sems[0] - # check lock - if not sem["etat"]: - raise ScoValueError("inscription: semestre verrouille") - # - r = _formsemestre_inscriptionEditor.create(cnx, args) - # Evenement - sco_etud.scolar_events_create( - cnx, - args={ - "etudid": args["etudid"], - "event_date": time.strftime(scu.DATE_FMT), - "formsemestre_id": args["formsemestre_id"], - "event_type": "INSCRIPTION", - }, - ) - # Log etudiant - Scolog.logdb( - method=method, - etudid=args["etudid"], - msg=f"inscription en semestre {args['formsemestre_id']}", - commit=True, - ) - # - sco_cache.invalidate_formsemestre(formsemestre_id=args["formsemestre_id"]) - return r - - def do_formsemestre_inscription_delete(oid, formsemestre_id=None): "delete formsemestre_inscription" cnx = ndb.GetDBConnexion() @@ -283,20 +246,18 @@ def do_formsemestre_inscription_with_modules( """Inscrit cet etudiant à ce semestre et TOUS ses modules STANDARDS (donc sauf le sport) Si dept_id est spécifié, utilise ce département au lieu du courant. + Vérifie la capacité d'accueil. """ + etud = Identite.get_etud(etudid) group_ids = group_ids or [] if isinstance(group_ids, int): group_ids = [group_ids] # Check that all groups exist before creating the inscription groups = [GroupDescr.query.get_or_404(group_id) for group_id in group_ids] formsemestre = FormSemestre.get_formsemestre(formsemestre_id, dept_id=dept_id) - # inscription au semestre + # Inscription au semestre args = {"formsemestre_id": formsemestre_id, "etudid": etudid} - if etat is not None: - args["etat"] = etat - if etape is not None: - args["etape"] = etape - do_formsemestre_inscription_create(args, method=method) + formsemestre.inscrit_etudiant(etud, etat=etat, etape=etape, method=method) log( f"""do_formsemestre_inscription_with_modules: etudid={ etudid} formsemestre_id={formsemestre_id}""" diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index db7143e13..1f45cd626 100755 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -978,8 +978,8 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None html_sco_header.html_sem_header( page_title, with_page_header=False, with_h2=False ), - f"""