From 3d34f9330d980054a3f2b8f9ca6879cb47e85cb7 Mon Sep 17 00:00:00 2001 From: ilona Date: Sun, 11 Aug 2024 23:24:40 +0200 Subject: [PATCH] =?UTF-8?q?Capacit=C3=A9=20d'accueil=20des=20formsemestre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/formsemestre.py | 65 +++++++++++++++++-- app/scodoc/sco_formsemestre.py | 1 + app/scodoc/sco_formsemestre_edit.py | 17 +++-- app/scodoc/sco_formsemestre_inscriptions.py | 47 ++------------ app/scodoc/sco_formsemestre_status.py | 17 +++-- app/static/css/scodoc.css | 9 +++ .../2640b7686de6_formsemestre_description.py | 6 +- 7 files changed, 106 insertions(+), 56 deletions(-) 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""" -
Formation: + f""" + + """ ) + if formsemestre.capacite_accueil is not None: + H.append( + f""" + + + + """ + ) evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre) H.append( - ' +
Formation : Parcours:
Parcours : {', '.join(parcours.code for parcours in sem_parcours)}
Capacité d'accueil : {formsemestre.capacite_accueil}
Évaluations: %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides' + """
Évaluations : %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides""" % evals ) if evals["last_modif"]: diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index b844a9d42..783ac6669 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -1844,6 +1844,15 @@ div.formsemestre_status { /* EMO_WARNING, "⚠️" */ } +table.formsemestre_status_head { + border-collapse: collapse; + +} + +table.formsemestre_status_head tr td:nth-child(2) { + padding-left: 1em; +} + table.formsemestre_status { border-collapse: collapse; } diff --git a/migrations/versions/2640b7686de6_formsemestre_description.py b/migrations/versions/2640b7686de6_formsemestre_description.py index 88ec43884..c275d3e22 100644 --- a/migrations/versions/2640b7686de6_formsemestre_description.py +++ b/migrations/versions/2640b7686de6_formsemestre_description.py @@ -1,4 +1,4 @@ -"""FormSemestreDescription +"""FormSemestreDescription et capacité d'accueil Revision ID: 2640b7686de6 Revises: f6cb3d4e44ec @@ -32,7 +32,11 @@ def upgrade(): ), sa.PrimaryKeyConstraint("id"), ) + with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op: + batch_op.add_column(sa.Column("capacite_accueil", sa.Integer(), nullable=True)) def downgrade(): + with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op: + batch_op.drop_column("capacite_accueil") op.drop_table("notes_formsemestre_description")