1
0
forked from ScoDoc/ScoDoc

Capacité d'accueil des formsemestre

This commit is contained in:
ilona 2024-08-11 23:24:40 +02:00
parent 525d0446cc
commit 3d34f9330d
7 changed files with 106 additions and 56 deletions

View File

@ -36,7 +36,7 @@ from app.models.config import ScoDocSiteConfig
from app.models.departements import Departement from app.models.departements import Departement
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.evaluations import Evaluation 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.formations import Formation
from app.models.groups import GroupDescr, Partition from app.models.groups import GroupDescr, Partition
from app.models.moduleimpls import ( from app.models.moduleimpls import (
@ -45,9 +45,10 @@ from app.models.moduleimpls import (
notes_modules_enseignants, notes_modules_enseignants,
) )
from app.models.modules import Module from app.models.modules import Module
from app.models.scolar_event import ScolarEvent
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.models.validations import ScolarFormSemestreValidation 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_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_utils import MONTH_NAMES_ABBREV, translate_assiduites_metric 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")) formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1") semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1")
titre = db.Column(db.Text(), nullable=False) 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_debut = db.Column(db.Date(), nullable=False)
date_fin = db.Column(db.Date(), nullable=False) # jour inclus date_fin = db.Column(db.Date(), nullable=False) # jour inclus
edt_id: str | None = db.Column(db.Text(), index=True, nullable=True) 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} codes |= {x.strip() for x in self.elt_passage_apo.split(",") if x}
return codes 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 """Liste des étudiants inscrits à ce semestre
Si include_demdef, tous les étudiants, avec les démissionnaires Si include_demdef, tous les étudiants, avec les démissionnaires
et défaillants. et défaillants.
Si etats, seuls les étudiants dans l'un des états indiqués.
Si order, tri par clé sort_key Si order, tri par clé sort_key
""" """
if include_demdef: if include_demdef:
etuds = [ins.etud for ins in self.inscriptions] 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] 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: if order:
etuds.sort(key=lambda e: e.sort_key) etuds.sort(key=lambda e: e.sort_key)
return etuds 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( def get_partitions_list(
self, with_default=True, only_listed=False self, with_default=True, only_listed=False
) -> list[Partition]: ) -> list[Partition]:

View File

@ -53,6 +53,7 @@ _formsemestreEditor = ndb.EditableTable(
"semestre_id", "semestre_id",
"formation_id", "formation_id",
"titre", "titre",
"capacite_accueil",
"date_debut", "date_debut",
"date_fin", "date_fin",
"gestion_compensation", "gestion_compensation",

View File

@ -350,8 +350,6 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
"labels": modalites_titles, "labels": modalites_titles,
}, },
), ),
]
modform.append(
( (
"semestre_id", "semestre_id",
{ {
@ -367,10 +365,21 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
"attributes": ['onchange="change_semestre_id();"'] if is_apc else "", "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() etapes = sco_portal_apogee.get_etapes_apogee_dept()
# Propose les etapes renvoyées par le portail # 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} etapes_set = {et[0] for et in etapes}
if edit: if edit:
for etape_vdi in formsemestre.etapes_apo_vdi(): for etape_vdi in formsemestre.etapes_apo_vdi():

View File

@ -85,43 +85,6 @@ def do_formsemestre_inscription_listinscrits(formsemestre_id):
return r 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): def do_formsemestre_inscription_delete(oid, formsemestre_id=None):
"delete formsemestre_inscription" "delete formsemestre_inscription"
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
@ -283,20 +246,18 @@ def do_formsemestre_inscription_with_modules(
"""Inscrit cet etudiant à ce semestre et TOUS ses modules STANDARDS """Inscrit cet etudiant à ce semestre et TOUS ses modules STANDARDS
(donc sauf le sport) (donc sauf le sport)
Si dept_id est spécifié, utilise ce département au lieu du courant. 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 [] group_ids = group_ids or []
if isinstance(group_ids, int): if isinstance(group_ids, int):
group_ids = [group_ids] group_ids = [group_ids]
# Check that all groups exist before creating the inscription # Check that all groups exist before creating the inscription
groups = [GroupDescr.query.get_or_404(group_id) for group_id in group_ids] groups = [GroupDescr.query.get_or_404(group_id) for group_id in group_ids]
formsemestre = FormSemestre.get_formsemestre(formsemestre_id, dept_id=dept_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id, dept_id=dept_id)
# inscription au semestre # Inscription au semestre
args = {"formsemestre_id": formsemestre_id, "etudid": etudid} args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
if etat is not None: formsemestre.inscrit_etudiant(etud, etat=etat, etape=etape, method=method)
args["etat"] = etat
if etape is not None:
args["etape"] = etape
do_formsemestre_inscription_create(args, method=method)
log( log(
f"""do_formsemestre_inscription_with_modules: etudid={ f"""do_formsemestre_inscription_with_modules: etudid={
etudid} formsemestre_id={formsemestre_id}""" etudid} formsemestre_id={formsemestre_id}"""

View File

@ -978,8 +978,8 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
page_title, with_page_header=False, with_h2=False page_title, with_page_header=False, with_h2=False
), ),
f"""<table> f"""<table class="formsemestre_status_head">
<tr><td class="fichetitre2">Formation: </td><td> <tr><td class="fichetitre2">Formation&nbsp;: </td><td>
<a href="{url_for('notes.ue_table', <a href="{url_for('notes.ue_table',
scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)}" scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)}"
class="discretelink" title="Formation { class="discretelink" title="Formation {
@ -1002,15 +1002,24 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
sem_parcours = formsemestre.get_parcours_apc() sem_parcours = formsemestre.get_parcours_apc()
H.append( H.append(
f""" f"""
<tr><td class="fichetitre2">Parcours: </td> <tr><td class="fichetitre2">Parcours&nbsp;: </td>
<td style="color: blue;">{', '.join(parcours.code for parcours in sem_parcours)}</td> <td style="color: blue;">{', '.join(parcours.code for parcours in sem_parcours)}</td>
</tr> </tr>
""" """
) )
if formsemestre.capacite_accueil is not None:
H.append(
f"""
<tr><td class="fichetitre2">Capacité d'accueil&nbsp;: </td>
<td>{formsemestre.capacite_accueil}</td>
</tr>
"""
)
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre) evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
H.append( H.append(
'<tr><td class="fichetitre2">Évaluations: </td><td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides' """<tr><td class="fichetitre2">Évaluations&nbsp;: </td>
<td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides"""
% evals % evals
) )
if evals["last_modif"]: if evals["last_modif"]:

View File

@ -1844,6 +1844,15 @@ div.formsemestre_status {
/* EMO_WARNING, "&#9888;&#65039;" */ /* EMO_WARNING, "&#9888;&#65039;" */
} }
table.formsemestre_status_head {
border-collapse: collapse;
}
table.formsemestre_status_head tr td:nth-child(2) {
padding-left: 1em;
}
table.formsemestre_status { table.formsemestre_status {
border-collapse: collapse; border-collapse: collapse;
} }

View File

@ -1,4 +1,4 @@
"""FormSemestreDescription """FormSemestreDescription et capacité d'accueil
Revision ID: 2640b7686de6 Revision ID: 2640b7686de6
Revises: f6cb3d4e44ec Revises: f6cb3d4e44ec
@ -32,7 +32,11 @@ def upgrade():
), ),
sa.PrimaryKeyConstraint("id"), 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(): 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") op.drop_table("notes_formsemestre_description")